const { useState, useEffect, useRef, useMemo, memo, createContext, useContext } = React;
const { createRoot } = ReactDOM;

// ==========================================
// 1. Enums & Types
// ==========================================
  
const OperatorType = { RANGED: '高台', MELEE: '地面' };
const Direction = { UP: 0, RIGHT: 1, DOWN: 2, LEFT: 3 };
const TAGS = [
    '近战位', '远程位', '输出', '生存', '治疗', '支援', '防护', '群攻', '减速', '控场', '削弱', '位移', '召唤', '快速复活', '爆发', '资深干员', '高级资深干员'
];

// ==========================================
// 2. Game Constants
// ==========================================

const RANGE_PATTERNS = {
    MELEE_SHORT: [{x:0,y:0}, {x:1,y:0}],
    MELEE_LONG: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}],
    SNIPER_BASIC: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:3,y:0}, {x:1,y:-1}, {x:2,y:-1}, {x:1,y:1}, {x:2,y:1}],
    SNIPER_LONG: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:3,y:0}, {x:4,y:0}],
    CASTER_BASIC: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:1,y:-1}, {x:1,y:1}, {x:2,y:-1}, {x:2,y:1}],
    MEDIC_BASIC: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:3,y:0}, {x:0,y:-1}, {x:1,y:-1}, {x:2,y:-1}, {x:0,y:1}, {x:1,y:1}, {x:2,y:1}],
    SPLASH: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:1,y:-1}, {x:1,y:1}, {x:0,y:-1}, {x:0,y:1}, {x:-1,y:0}],
    DIAMOND: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:0,y:-1}, {x:0,y:1}, {x:1,y:-1}, {x:1,y:1}],
    THORNS: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}, {x:0,y:-1}, {x:0,y:1}, {x:1,y:-1}, {x:1,y:1}, {x:2,y:-1}, {x:2,y:1}],
    SURTR: [{x:0,y:0}, {x:1,y:0}, {x:2,y:0}],
    SURROUND_8: [{x:0,y:0}, {x:1,y:0}, {x:-1,y:0}, {x:0,y:1}, {x:0,y:-1}, {x:1,y:1}, {x:1,y:-1}, {x:-1,y:1}, {x:-1,y:-1}],
    SURROUND_24: (function(){const a=[];for(let y=-2;y<=2;y++)for(let x=-2;x<=2;x++)if(x!==0||y!==0)a.push({x,y});return a;})(),
    KALTSIT_TRUE: (function(){const a=[];for(let y=-1;y<=1;y++)for(let x=0;x<=6;x++)a.push({x,y});return a;})(),
};

const SKINS = [
    { id: 'skin_silver_summer', operatorId: 'silver_slash', name: '探索者', cost: 18 },
    { id: 'skin_eyja_summer', operatorId: 'eyja_volcano', name: '夏卉', cost: 18 },
    { id: 'skin_exu_kfc', operatorId: 'exu_angel', name: '城市骑手', cost: 15 },
    { id: 'skin_mud_summer', operatorId: 'mud_rock', name: '静谧午夜', cost: 18 },
    { id: 'skin_surtr_liberte', operatorId: 'op_surtr', name: '自由', cost: 21 },
    { id: 'skin_thorns_shopping', operatorId: 'op_thorns', name: '舒适', cost: 18 },
    { id: 'skin_wisadel_e2', operatorId: 'sniper_wisadel', name: '流浪者', cost: 24 },
    { id: 'skin_mizuki_idol', operatorId: 'spec_mizuki_7', name: '偶像时刻', cost: 24 },
    { id: 'skin_plat_knight', operatorId: 'def_platinum_7', name: '逐魇骑士', cost: 24 },
];

const TASKS = [
    { id: 'daily_login', title: '每日签到', desc: '登录游戏', target: 1, reward: { lmd: 2000 } },
    { id: 'daily_battle', title: '日常作战', desc: '完成1次战斗', target: 1, reward: { orundum: 100 } },
    { id: 'daily_kill', title: '清理威胁', desc: '累计击败20名敌人', target: 20, reward: { lmd: 5000, recruitTicket: 1 } },
    { id: 'weekly_recruit', title: '招募新人', desc: '进行3次公开招募', target: 3, reward: { recruitTicket: 5 } },
];

// damageType: 0: Physical, 1: Arts, 2: True, 3: Healing
const OPERATORS = [
    // 7 STAR
      { 
        id: 'supporter_xingyuan', 
        name: '星缘', 
        rarity: 7, 
        class: 'Supporter', 
        type: OperatorType.RANGED, 
        tags: ['远程位', '支援', '削弱', '高级资深干员'],
        description: '星之祈愿', 
        skill: '常态不攻击。每15秒切换阶段：强化友军->扩大范围->削弱敌人->终极形态(攻击力=范围内友军之和)',
        baseStats: { 
            hp: 2000, atk: 0, def: 200, res: 20, cost: 12, block: 1, 
            attackInterval: 60, redeployTime: 60, rangePattern: RANGE_PATTERNS.SURROUND_8, canHitFlying: true, damageType: 1 
        },
        isXingyuan: true
    },
    { 
        id: 'spec_mizuki_7', 
        name: '弥棠蓝毒', 
        rarity: 7, 
        class: 'Specialist', 
        type: OperatorType.MELEE, 
        tags: ['近战位', '快速复活', '输出', '高级资深干员'],
        description: '剧毒拟态', 
        skill: '攻击变为4连击，对周围8格施加剧毒',
        baseStats: { 
            hp: 1800, atk: 650, def: 150, res: 30, cost: 8, block: 1, 
            attackInterval: 240, redeployTime: 15, rangePattern: RANGE_PATTERNS.SURROUND_8, canHitFlying: true, damageType: 1 
        },
        isBluePoison: true,
        spType: 'auto', maxSp: 3
    },
    { 
        id: 'def_platinum_7', 
        name: '平板白金', 
        rarity: 7, 
        class: 'Defender', 
        type: OperatorType.MELEE, 
        tags: ['近战位', '防护', '治疗', '高级资深干员'],
        description: '天马护卫', 
        skill: '受击5次后治疗周围友军，阻挡数6，容易受击',
        baseStats: { 
            hp: 4500, atk: 500, def: 850, res: 15, cost: 34, block: 6, 
            attackInterval: 96, redeployTime: 70, rangePattern: [{x:0,y:0}], canHitFlying: false, damageType: 0
        },
        isPlatinumDefender: true,
        taunt: true
    },
    { 
        id: 'sniper_wisadel', 
        name: '维什戴尔', 
        rarity: 7, 
        class: 'Sniper', 
        type: OperatorType.RANGED, 
        description: '爆裂黎明', 
        tags: ['远程位', '群攻', '召唤', '爆发', '高级资深干员'],
        skill: '攻回3点SP后开启爆裂黎明，获得6发弹药，攻击造成大范围溅射伤害。并在周围召唤魂灵之影，若魂灵之影存在则自身隐匿。', 
        baseStats: { 
            hp: 2500, atk: 1000, def: 200, res: 20, cost: 30, block: 1, 
            attackInterval: 120, redeployTime: 70, rangePattern: RANGE_PATTERNS.SPLASH, canHitFlying: true, damageType: 0
        },
        isWisadel: true,
        spType: 'attack', maxSp: 3
    },
    { 
        id: 'vanguard_myrtle_7', 
        name: '桃金娘(极)', 
        rarity: 7, 
        class: 'Vanguard', 
        type: OperatorType.MELEE, 
        description: '黄金大将军', 
        tags: ['近战位', '费用回复', '治疗', '高级资深干员'],
        skill: '存活时大幅增加费用回复', 
        baseStats: { 
            hp: 1800, atk: 400, def: 200, res: 0, cost: 6, block: 1, 
            attackInterval: 70, redeployTime: 60, rangePattern: RANGE_PATTERNS.MELEE_SHORT, damageType: 3
        } 
    },
    { 
        id: 'spec_xuhuan_7', 
        name: '珝环', 
        rarity: 7, 
        class: 'Specialist', 
        type: OperatorType.MELEE, 
        description: '替身陷阱', 
        tags: ['近战位', '快速复活', '生存', '高级资深干员'],
        skill: '死亡后10秒原地复活，阻挡0，期间对周围造成真伤，若复活期间倒下则撤退', 
        baseStats: { 
            hp: 2000, atk: 600, def: 300, res: 10, cost: 5, block: 1, 
            attackInterval: 80, redeployTime: 20, rangePattern: RANGE_PATTERNS.DIAMOND, damageType: 0 
        } 
    },
      
    // 6 STAR
      { 
        id: 'supporter_slow', 
        name: '灵知', 
        rarity: 6, 
        class: 'Supporter', 
        type: OperatorType.RANGED, 
        description: '极寒', 
        tags: ['远程位', '减速', '削弱', '输出', '高级资深干员'],
        skill: '攻击造成冻结效果', 
        baseStats: { 
            hp: 1600, atk: 500, def: 150, res: 25, cost: 14, block: 1, 
            attackInterval: 90, redeployTime: 60, rangePattern: RANGE_PATTERNS.CASTER_BASIC, canHitFlying: true, damageType: 1
        } 
    },
    { 
        id: 'silver_slash', 
        name: '银灰', 
        rarity: 6, 
        class: 'Guard', 
        type: OperatorType.MELEE, 
        description: '真银斩', 
        tags: ['近战位', '输出', '支援', '高级资深干员'],
        skill: '攻击范围扩大，攻击力大幅提升，可以对空', 
        baseStats: { 
            hp: 2200, atk: 750, def: 350, res: 10, cost: 20, block: 2, 
            attackInterval: 80, redeployTime: 60, rangePattern: RANGE_PATTERNS.MELEE_LONG, canHitFlying: true, damageType: 0
        } 
    },
    { 
        id: 'eyja_volcano', 
        name: '艾雅法拉', 
        rarity: 6, 
        class: 'Caster', 
        type: OperatorType.RANGED, 
        description: '火山', 
        tags: ['远程位', '输出', '削弱', '高级资深干员'],
        skill: '攻击造成法术伤害，由于是单体术师攻速较快', 
        baseStats: { 
            hp: 1600, atk: 700, def: 120, res: 20, cost: 22, block: 1, 
            attackInterval: 100, redeployTime: 70, rangePattern: RANGE_PATTERNS.CASTER_BASIC, canHitFlying: true, damageType: 1
        } 
    },
    { 
        id: 'exu_angel', 
        name: '能天使', 
        rarity: 6, 
        class: 'Sniper', 
        type: OperatorType.RANGED, 
        description: '过载模式', 
        tags: ['远程位', '输出', '高级资深干员'],
        skill: '攻击变为5连射', 
        baseStats: { 
            hp: 1400, atk: 550, def: 150, res: 0, cost: 14, block: 1, 
            attackInterval: 40, redeployTime: 60, rangePattern: RANGE_PATTERNS.SNIPER_BASIC, canHitFlying: true, damageType: 0
        } 
    },
    { 
        id: 'mud_rock', 
        name: '泥岩', 
        rarity: 6, 
        class: 'Defender', 
        type: OperatorType.MELEE, 
        description: '沃土', 
        tags: ['近战位', '防护', '生存', '高级资深干员'],
        skill: '每9秒获得护盾，护盾破碎回复生命', 
        baseStats: { 
            hp: 4000, atk: 800, def: 600, res: 10, cost: 32, block: 3, 
            attackInterval: 100, redeployTime: 60, rangePattern: [{x:0,y:0}], damageType: 0
        } 
    },
    { 
        id: 'skadi_tide', 
        name: '斯卡蒂', 
        rarity: 6, 
        class: 'Guard', 
        type: OperatorType.MELEE, 
        description: '涌潮悲歌', 
        tags: ['近战位', '生存', '输出', '高级资深干员'],
        skill: '攻击力极高，但防御一般', 
        baseStats: { 
            hp: 3500, atk: 1000, def: 300, res: 0, cost: 17, block: 1, 
            attackInterval: 90, redeployTime: 60, rangePattern: RANGE_PATTERNS.MELEE_SHORT, damageType: 0
        } 
    },
    { 
        id: 'op_surtr', 
        name: '史尔特尔', 
        rarity: 6, 
        class: 'Guard', 
        type: OperatorType.MELEE, 
        description: '黄昏', 
        tags: ['近战位', '输出', '生存', '高级资深干员'],
        skill: '生命值强制不低于1，持续20秒后退场', 
        baseStats: { 
            hp: 3000, atk: 900, def: 200, res: 20, cost: 21, block: 1, 
            attackInterval: 80, redeployTime: 70, rangePattern: RANGE_PATTERNS.SURTR, canHitFlying: false, damageType: 1
        } 
    },
    { 
        id: 'op_thorns', 
        name: '棘刺', 
        rarity: 6, 
        class: 'Guard', 
        type: OperatorType.MELEE, 
        description: '至高之术', 
        tags: ['近战位', '输出', '防护', '高级资深干员'],
        skill: '攻击附带毒伤，第二次开启后范围扩大', 
        baseStats: { 
            hp: 2400, atk: 700, def: 400, res: 0, cost: 20, block: 2, 
            attackInterval: 78, redeployTime: 60, rangePattern: RANGE_PATTERNS.THORNS, canHitFlying: true, damageType: 1
        },
        spType: 'attack', maxSp: 15
    },
    { 
        id: 'op_saria', 
        name: '塞雷娅', 
        rarity: 6, 
        class: 'Defender', 
        type: OperatorType.MELEE, 
        description: '钙质化', 
        tags: ['近战位', '防护', '治疗', '支援', '高级资深干员'],
        skill: '治疗周围友军并减速敌人', 
        baseStats: { 
            hp: 3200, atk: 500, def: 600, res: 20, cost: 22, block: 3, 
            attackInterval: 100, redeployTime: 60, rangePattern: RANGE_PATTERNS.DIAMOND, damageType: 3
        } 
    },
    { 
        id: 'op_kaltsit', 
        name: '凯尔希', 
        rarity: 6, 
        class: 'Medic', 
        type: OperatorType.RANGED, 
        description: 'Mon3tr', 
        tags: ['远程位', '治疗', '召唤', '输出', '高级资深干员'],
        skill: '召唤Mon3tr协助作战，真伤输出，超广域治疗', 
        baseStats: { 
            hp: 1800, atk: 500, def: 200, res: 0, cost: 20, block: 1, 
            attackInterval: 120, redeployTime: 60, rangePattern: RANGE_PATTERNS.KALTSIT_TRUE, damageType: 2
        } 
    },
    { 
        id: 'op_chen', 
        name: '陈', 
        rarity: 6, 
        class: 'Guard', 
        type: OperatorType.MELEE, 
        description: '绝影', 
        tags: ['近战位', '爆发', '输出', '高级资深干员'],
        skill: '对周围敌人进行10次连续斩击', 
        baseStats: { 
            hp: 2500, atk: 650, def: 400, res: 0, cost: 19, block: 2, 
            attackInterval: 70, redeployTime: 60, rangePattern: RANGE_PATTERNS.MELEE_SHORT, damageType: 0
        },
        spType: 'attack', maxSp: 20
    },

    // 5 STAR
    { 
        id: 'texas_pioneer', 
        name: '德克萨斯', 
        rarity: 5, 
        class: 'Vanguard', 
        type: OperatorType.MELEE, 
        description: '剑雨', 
        tags: ['近战位', '费用回复', '控场', '资深干员'],
        skill: '攻击两次，带眩晕', 
        baseStats: { 
            hp: 1800, atk: 500, def: 300, res: 0, cost: 8, block: 2, 
            attackInterval: 60, redeployTime: 60, rangePattern: RANGE_PATTERNS.MELEE_SHORT, damageType: 1
        },
        spType: 'auto', maxSp: 40
    },
    { 
        id: 'medic_silence', 
        name: '赫默', 
        rarity: 5, 
        class: 'Medic', 
        type: OperatorType.RANGED, 
        description: '无人机', 
        tags: ['远程位', '治疗', '资深干员'],
        skill: '可以部署无人机治疗队友', 
        baseStats: { 
            hp: 1500, atk: 450, def: 120, res: 10, cost: 18, block: 1, 
            attackInterval: 120, redeployTime: 60, rangePattern: RANGE_PATTERNS.MEDIC_BASIC, canHitFlying: false, damageType: 3
        } 
    },
      
    // 4 STAR
      { 
        id: 'spec_push', 
        name: '阿消', 
        rarity: 4, 
        class: 'Specialist', 
        type: OperatorType.MELEE, 
        description: '高压水炮', 
        tags: ['近战位', '位移', '防护'],
        skill: '攻击推开敌人',
        isPusher: true,
        baseStats: { 
            hp: 1600, atk: 500, def: 350, res: 0, cost: 17, block: 2, 
            attackInterval: 80, redeployTime: 18, rangePattern: [{x:0,y:0},{x:1,y:0},{x:2,y:0}], damageType: 1
        },
        spType: 'auto', maxSp: 5
    },
    { 
        id: 'spec_pull', 
        name: '暗索', 
        rarity: 4, 
        class: 'Specialist', 
        type: OperatorType.RANGED, 
        description: '勾爪', 
        tags: ['远程位', '位移', '输出'],
        skill: '将敌人拉向自己',
        isPuller: true,
        baseStats: { 
            hp: 1400, atk: 450, def: 120, res: 0, cost: 15, block: 1, 
            attackInterval: 80, redeployTime: 18, rangePattern: [{x:0,y:0},{x:1,y:0},{x:2,y:0}], damageType: 0
        },
        spType: 'auto', maxSp: 5
    },
    { 
        id: 'def_cuora', 
        name: '蛇屠箱', 
        rarity: 4, 
        class: 'Defender', 
        type: OperatorType.MELEE, 
        description: '龟壳防御', 
        tags: ['近战位', '防护'],
        skill: '阻挡数+1，防御力大幅提升', 
        baseStats: { 
            hp: 2600, atk: 350, def: 650, res: 0, cost: 18, block: 4, 
            attackInterval: 90, redeployTime: 60, rangePattern: [{x:0,y:0}], canHitFlying: false, damageType: 0
        } 
    },
    { 
        id: 'specialist_box', 
        name: '罗宾', 
        rarity: 4, 
        class: 'Specialist', 
        type: OperatorType.MELEE, 
        description: '夹子', 
        tags: ['近战位', '控场', '输出'],
        skill: '可以使用陷阱', 
        baseStats: { 
            hp: 1500, atk: 400, def: 300, res: 0, cost: 8, block: 1, 
            attackInterval: 70, redeployTime: 18, rangePattern: RANGE_PATTERNS.SNIPER_BASIC, canHitFlying: true, damageType: 0
        } 
    },
      
    // 3 STAR
    { 
        id: 'sniper_kroos', 
        name: '克洛丝', 
        rarity: 3, 
        class: 'Sniper', 
        type: OperatorType.RANGED, 
        description: 'kokodayo', 
        tags: ['远程位', '输出'],
        skill: '攻击必定暴击', 
        baseStats: { 
            hp: 1100, atk: 380, def: 110, res: 0, cost: 10, block: 1, 
            attackInterval: 60, redeployTime: 60, rangePattern: RANGE_PATTERNS.SNIPER_BASIC, canHitFlying: true, damageType: 0
        },
        spType: 'attack', maxSp: 4
    },
    { 
        id: 'van_plume', 
        name: '翎羽', 
        rarity: 3, 
        class: 'Vanguard', 
        type: OperatorType.MELEE, 
        description: '冲锋', 
        tags: ['近战位', '费用回复', '输出'],
        skill: '撤退返还全部费用', 
        baseStats: { 
            hp: 1400, atk: 450, def: 200, res: 0, cost: 4, block: 1, 
            attackInterval: 70, redeployTime: 60, rangePattern: RANGE_PATTERNS.MELEE_SHORT, damageType: 0
        } 
    },
];

const SUMMONS = {
    'spirit_shadow': { 
        id: 'spirit_shadow', name: '魂灵之影', hp: 3000, atk: 600, def: 400, res: 30, block: 0, 
        rangePattern: RANGE_PATTERNS.MELEE_SHORT, 
        baseStats: { hp: 3000, atk: 600, def: 400, res: 30, block: 0, damageType: 0 }
    }
};

const ENEMY_TYPES = {
    soldier: { name: '士兵', hp: 1500, atk: 250, def: 100, res: 0, speed: 0.8, attackInterval: 2.0, description: "普通的整合运动士兵，威胁较低。" },
    dog: { name: '源石虫', hp: 900, atk: 350, def: 0, res: 0, speed: 1.4, attackInterval: 1.5, description: "移动速度较快的感染生物，建议优先处理。" },
    shield: { name: '重装卫士', hp: 6000, atk: 400, def: 800, res: 0, speed: 0.4, attackInterval: 3.0, description: "防御力极高的重装单位，需要法术伤害应对。" },
    caster: { name: '术师', hp: 2500, atk: 500, def: 150, res: 50, speed: 0.7, attackInterval: 2.5, range: 3, description: "能够造成远程法术伤害的敌人。" },
    wraith: { name: '幽灵', hp: 2500, atk: 200, def: 150, res: 30, speed: 1.8, attackInterval: 1.0, description: "无法被阻挡，移动速度极快，必须快速击杀。" },
    butcher: { name: '屠夫', hp: 10000, atk: 900, def: 200, res: 10, speed: 0.5, attackInterval: 3.5, description: "拥有高生命值和攻击力的精英单位。" },
      
    // Elite / Special
    drone: { name: '威龙', hp: 3000, atk: 400, def: 150, res: 0, speed: 1.0, attackInterval: 2.0, isFlying: true, range: 2.5, description: "空中单位，只能被对空干员攻击。" },
    gargoyle: { name: '石像鬼', hp: 12000, atk: 800, def: 600, res: 20, speed: 0.6, attackInterval: 3.5, isFlying: true, description: "高防御空中单位，落地后会变为地面单位。" },
    elite_avenger: { name: '复仇者', hp: 15000, atk: 800, def: 300, res: 50, speed: 0.8, attackInterval: 1.8, description: "生命值低于一半时攻击力大幅提升。" },
    colossus: { name: '巨像', hp: 50000, atk: 2000, def: 1000, res: 0, speed: 0.3, attackInterval: 5.0, description: "巨大的构造体，攻击力极高且十分耐打。" },
    invisible_assassin: { name: '幽影杀手', hp: 2200, atk: 600, def: 100, res: 20, speed: 0.9, attackInterval: 1.8, invisible: true, description: "未被阻挡时处于隐匿状态，无法被选为目标。" },
    swarm_drone: { name: '机群无人机', hp: 10, atk: 200, def: 9999, res: 99, speed: 0.8, attackInterval: 1.5, isFlying: true, hitCount: true, description: "受到攻击只扣1点生命值，需要高频攻击应对。" },
    boss_patriot: { name: '爱国者', hp: 45000, atk: 1800, def: 1500, res: 60, speed: 0.3, attackInterval: 4.0, range: 1.5, description: "传说中的温迪戈，拥有极高的双抗和毁灭性的力量。" }, 
};

const ACHIEVEMENTS = [
    { id: 'kill_10', title: '第一滴血', desc: '击败10个敌人', reward: { orundum: 100 }, condition: (s) => s.stats.totalKills >= 10 },
    { id: 'kill_100', title: '杀戮者', desc: '击败100个敌人', reward: { orundum: 500 }, condition: (s) => s.stats.totalKills >= 100 },
    { id: 'kill_1000', title: '终结者', desc: '击败1000个敌人', reward: { orundum: 2000 }, condition: (s) => s.stats.totalKills >= 1000 },
    { id: 'win_5', title: '战术家', desc: '完成5次作战', reward: { orundum: 600 }, condition: (s) => s.stats.totalWins >= 5 },
    { id: 'rich', title: '大亨', desc: '拥有10万龙门币', reward: { orundum: 1000 }, condition: (s) => s.resources.lmd >= 100000 },
    { id: 'collector', title: '收藏家', desc: '拥有10名干员', reward: { orundum: 1000 }, condition: (s) => s.roster.length >= 10 },
    { id: 'clear_main', title: '精英干员', desc: '通关所有主线关卡', reward: { orundum: 5000 }, condition: (s) => s.completedLevels && LEVELS.filter(l=>!l.id.startsWith('C')&&l.id!=='endless').every(l=>s.completedLevels.includes(l.id)) },
    { id: 'endless_5', title: '坚守', desc: '无尽模式坚守5波', reward: { orundum: 1000 }, condition: (s) => s.stats.maxEndlessWave >= 5 },
    { id: 'endless_10', title: '铁壁', desc: '无尽模式坚守10波', reward: { orundum: 2000 }, condition: (s) => s.stats.maxEndlessWave >= 10 },
    { id: 'lvl_50', title: '资深干员', desc: '任意干员等级达到50', reward: { lmd: 50000 }, condition: (s) => s.roster.some(o=>o.level>=50) },
    { id: 'lvl_100', title: '传说干员', desc: '任意干员等级达到100', reward: { lmd: 100000 }, condition: (s) => s.roster.some(o=>o.level>=100) },
    { id: 'all_7star', title: '群星闪耀', desc: '集齐所有7星干员', reward: { originium: 10 }, condition: (s) => { const all7 = OPERATORS.filter(o=>o.rarity===7 && !o.isXingyuan).map(o=>o.id); return all7.every(id=>s.roster.some(r=>r.id===id)); } },
    { id: 'custom_clear', title: '设计师', desc: '通关一次自定义关卡', reward: { recruitTicket: 5 }, condition: (s) => s.stats.customClears > 0 },
    { id: 'pit_kill', title: '地形杀', desc: '让一个敌人掉入深坑', reward: { orundum: 200 }, condition: (s) => s.stats.pitKills > 0 },
    // Hidden
    { id: 'hidden_pity', title: '非酋之证', desc: '50抽未获得7星干员', reward: { originium: 50 }, condition: (s) => s.pityCounter >= 50, hidden: true },
    { id: 'hidden_xingyuan', title: '星之祈愿', desc: '获得彩蛋干员星缘', reward: { originium: 5 }, condition: (s) => s.roster.some(o=>o.id==='supporter_xingyuan'), hidden: true },
    { id: 'hidden_luck', title: '欧皇附体', desc: '单抽获得7星干员', reward: { orundum: 6000 }, condition: (s) => s.stats.luckyPull, hidden: true },
    { id: 'hidden_max', title: '登峰造极', desc: '获得一个满潜能7星干员', reward: { originium: 20 }, condition: (s) => s.roster.some(o=> OPERATORS.find(base=>base.id===o.id).rarity===7 && o.potential>=6), hidden: true },
];

const LEVELS = [
    { id: '1-1', name: '1-1 切尔诺伯格', desc: '基础训练，阻挡敌人', map: [[0,0,0,0,0,1,1,1,0,0,0,0],[0,0,0,0,0,1,1,1,0,0,0,0],[2,0,0,0,0,0,0,0,0,0,0,3],[2,0,0,0,0,0,0,0,0,0,0,3],[0,0,0,0,0,1,1,1,0,0,0,0],[0,0,0,0,0,1,1,1,0,0,0,0]], waves: [{ type: 'soldier', count: 5, interval: 3.0, rest: 5 }, { type: 'dog', count: 8, interval: 1.5, rest: 5 }, { type: 'soldier', count: 8, interval: 2.0, rest: 0 }] },
    { id: '1-2', name: '1-2 摧枯拉朽', desc: '大量源石虫来袭', map: [[1,1,1,0,0,0,0,0,0,1,1,1],[2,0,0,0,1,1,1,1,0,0,0,3],[1,1,1,0,0,0,0,0,0,1,1,1],[2,0,0,0,1,1,1,1,0,0,0,3],[1,1,1,0,0,0,0,0,0,1,1,1],[1,1,1,1,1,0,0,0,1,1,1,1]], waves: [{ type: 'dog', count: 20, interval: 1.2, rest: 5 }, { type: 'soldier', count: 10, interval: 2.0, rest: 5 }, { type: 'shield', count: 2, interval: 6, rest: 0 }] },
    { id: '1-3', name: '1-3 空袭', desc: '检测到飞行单位，请使用狙击干员', map: [[1,0,0,0,1,0,0,0,1,0,0,3],[2,0,1,0,0,0,1,0,0,0,1,3],[0,0,1,1,0,0,0,0,1,1,0,0],[0,0,1,1,0,0,0,0,1,1,0,0],[2,0,1,0,0,0,1,0,0,0,1,3],[1,0,0,0,1,0,0,0,1,0,0,3]], waves: [{ type: 'dog', count: 10, interval: 1.5, rest: 5 }, { type: 'drone', count: 6, interval: 3.0, rest: 5 }, { type: 'soldier', count: 5, interval: 2.5, rest: 0 }] },
    { id: '1-4', name: '1-4 决战', desc: '爱国者正在接近，准备决战', map: [[3,0,0,0,1,1,1,1,0,0,0,3],[0,0,0,0,1,0,0,1,0,0,0,0],[1,1,1,0,0,0,0,0,0,1,1,1],[2,0,0,0,1,1,1,1,0,0,0,2],[0,0,0,0,0,0,0,0,0,0,0,0],[1,1,1,1,1,1,1,1,1,1,1,1]], waves: [{ type: 'butcher', count: 4, interval: 6, rest: 15 }, { type: 'elite_avenger', count: 2, interval: 10, rest: 10 }, { type: 'boss_patriot', count: 1, interval: 1, rest: 0 }] },
    { id: '2-5', name: '2-5 幽影', desc: '检测到隐匿单位，请使用阻挡或反隐干员', map: [[1,1,1,0,0,0,0,0,1,1,1,3],[2,0,0,0,1,1,1,0,0,0,1,3],[1,1,1,0,0,0,0,0,1,0,1,3],[9,9,9,9,9,9,9,9,1,0,1,1],[2,0,0,0,0,0,0,0,0,0,0,3],[1,1,1,1,1,1,1,1,1,1,1,1]], waves: [{ type: 'invisible_assassin', count: 6, interval: 4.0, rest: 8 }, { type: 'soldier', count: 10, interval: 2.0, rest: 0 }] },
    { id: '3-8', name: '3-8 天灾', desc: '爱国者正在接近，包含特殊无人机', map: [[3,0,0,0,1,1,1,1,0,0,0,3],[0,0,0,0,1,0,0,1,0,0,0,0],[1,1,1,0,0,0,0,0,0,1,1,1],[2,0,0,0,1,1,1,1,0,0,0,2],[0,0,0,0,0,0,0,0,0,0,0,0],[1,1,1,1,1,1,1,1,1,1,1,1]], waves: [{ type: 'swarm_drone', count: 10, interval: 2.5, rest: 15 }, { type: 'shield', count: 5, interval: 4.0, rest: 10 }, { type: 'boss_patriot', count: 1, interval: 1, rest: 0 }] },
    { id: 'CE-5', name: 'CE-5 货物运送', desc: '由于防守薄弱，敌人会从四面八方涌来', map: [[2,0,0,0,1,1,1,1,0,0,0,2],[0,0,0,0,1,3,3,1,0,0,0,0],[1,1,1,0,0,3,3,0,0,0,1,1],[1,1,1,0,0,0,0,0,0,0,1,1],[0,0,0,0,1,1,1,1,0,0,0,0],[2,0,0,0,1,1,1,1,0,0,0,2]], waves: [{ type: 'soldier', count: 20, interval: 2.0, rest: 10 }, { type: 'shield', count: 10, interval: 3.0, rest: 5 }, { type: 'butcher', count: 5, interval: 5.0, rest: 0 }] }
];

function uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); }

const isWalkable = (x, y, ct, cd, isFlying = false) => {
    if (y < 0 || y >= ct.length || x < 0 || x >= ct[0].length) return false;
    if (isFlying) return true; 
    if (ct[y][x] === 1 || ct[y][x] === 9) return false; 
    if (cd.find(d => d.x === x && d.y === y && d.operatorId.includes('box'))) return false;
    return true;
};

const findPath = (sx, sy, ct, cd, target = null, isFlying = false) => {
    const q = [{x:sx, y:sy, path: []}]; const v = new Set([`${sx},${sy}`]);
    while(q.length > 0) {
        const head = q.shift(); if(!head) break;
        const {x, y, path} = head;
        if(target ? (Math.abs(x - target.x) < 0.5 && Math.abs(y - target.y) < 0.5) : ct[y][x] === 3) return [...path, {x, y}];
        const dirs = [[0,1],[0,-1],[1,0],[-1,0]];
        for(const [dx, dy] of dirs) {
            const nx = x + dx, ny = y + dy;
            if(isWalkable(nx, ny, ct, cd, isFlying) && !v.has(`${nx},${ny}`)) { v.add(`${nx},${ny}`); q.push({x: nx, y: ny, path: [...path, {x: nx, y: ny}]}); }
        }
    }
    if (isFlying && target) { const dx = target.x - sx; const dy = target.y - sy; return Math.abs(dx) > Math.abs(dy) ? [{x: sx + Math.sign(dx), y: sy}] : [{x: sx, y: sy + Math.sign(dy)}]; }
    return [];
};

const getGridRange = (unit, pattern) => {
    const cells = []; const ux = Math.round(unit.x); const uy = Math.round(unit.y); if(!pattern) return [{x:ux, y:uy}];
    for(const p of pattern) {
        let dx = p.x, dy = p.y, rotatedDx = 0, rotatedDy = 0;
        switch (unit.direction) {
            case Direction.RIGHT: rotatedDx = dx; rotatedDy = dy; break;
            case Direction.LEFT: rotatedDx = -dx; rotatedDy = -dy; break;
            case Direction.DOWN: rotatedDx = -dy; rotatedDy = dx; break;
            case Direction.UP: rotatedDx = dy; rotatedDy = -dx; break;
        }
        cells.push({x: ux+rotatedDx, y: uy+rotatedDy});
    }
    return cells;
};
const isInRange = (unit, pattern, tx, ty) => getGridRange(unit, pattern).some(r => Math.abs(r.x - tx) < 0.5 && Math.abs(r.y - ty) < 0.5);
const encodeMapData = (level) => { try { return window.btoa(window.encodeURIComponent(JSON.stringify({ n: level.name, d: level.desc || "", m: level.map, w: level.waves }))); } catch (e) { return null; } };
const decodeMapData = (code) => { try { const data = JSON.parse(window.decodeURIComponent(window.atob(code))); if (!data.m || !data.w) throw new Error(); return { id: `C-${Date.now()}`, name: data.n || "Imported", desc: data.d || "", map: data.m, waves: data.w, isCustom: true }; } catch (e) { return null; } };

const Icons = {
    Play: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>,
    Pause: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>,
    Zap: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>,
    Shield: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>,
    Database: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>,
    Hammer: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M13.5 6.5l-4-4"></path><path d="M4 10.5l4 4"></path><path d="M8 18.5l4-4"></path><path d="M17.5 11l-4-4"></path><path d="M21 21l-7.5-7.5"></path><path d="M18.5 5.5l2 2"></path><path d="M18.5 8.5l-3-3"></path></svg>,
    Map: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"></polygon><line x1="8" y1="2" x2="8" y2="18"></line><line x1="16" y1="6" x2="16" y2="22"></line></svg>,
    Users: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>,
    ArrowLeft: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>,
    BrainCircuit: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"></path><path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"></path><path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"></path><path d="M17.599 6.5a3 3 0 0 0 .399-1.375"></path><path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"></path><path d="M3.477 12.578a3 3 0 0 1 .391-1.378"></path><path d="M20.132 11.2a3 3 0 0 1 .39 1.378"></path></svg>,
    X: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>,
    Plus: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>,
    Check: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="20 6 9 17 4 12"></polyline></svg>,
    Trash: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>,
    Sword: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polygon points="14.5 17.5 3 6 3 3 6 3 17.5 14.5"></polygon><line x1="13" y1="19" x2="19" y2="13"></line><line x1="16" y1="16" x2="20" y2="20"></line><line x1="19" y1="21" x2="21" y2="19"></line></svg>,
    Target: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="6"></circle><circle cx="12" cy="12" r="2"></circle></svg>,
    Flag: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"></path><line x1="4" y1="22" x2="4" y2="15"></line></svg>,
    Move: ({className, size=24}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="5 9 2 12 5 15"></polyline><polyline points="9 5 12 2 15 5"></polyline><polyline points="19 9 22 12 19 15"></polyline><polyline points="15 19 12 22 9 19"></polyline><line x1="2" y1="12" x2="22" y2="12"></line><line x1="12" y1="2" x2="12" y2="22"></line></svg>,
    Package: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>,
    DollarSign: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="12" y1="1" x2="12" y2="23"></line><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"></path></svg>,
    Lock: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>,
    Star: ({size=24, fill, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill={fill||"none"} stroke="currentColor" strokeWidth="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>,
    ShoppingCart: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>,
    Gem: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 3h12l4 6-10 13L2 9Z"></path></svg>,
    RefreshCw: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path><path d="M8 16H3v5"></path></svg>,
    Book: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path></svg>,
    Clock: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>,
    LogOut: ({size=24}) => <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>,
    Trophy: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"></path><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"></path><path d="M4 22h16"></path><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"></path><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"></path><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"></path></svg>,
    Edit: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>,
    ArrowUp: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>,
    InfinityIcon: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 12c-2-2.67-4-4-6-4a4 4 0 1 0 0 8c2 0 4-1.33 6-4Zm0 0c2 2.67 4 4 6 4a4 4 0 0 0 0-8c-2 0-4 1.33-6 4Z"></path></svg>,
    Share: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8"></path><polyline points="16 6 12 2 8 6"></polyline><line x1="12" y1="2" x2="12" y2="15"></line></svg>,
    Download: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>,
    Ticket: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"></path></svg>,
    Save: ({size=24, className}) => <svg className={className} width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path><polyline points="17 21 17 13 7 13 7 21"></polyline><polyline points="7 3 7 8 15 8"></polyline></svg>,
    ClassIcon: ({ cls, className, size }) => {
        const s = size || 16;
        switch(cls) {
            case 'Vanguard': return <Icons.Flag className={className} size={s} />;
            case 'Guard': return <Icons.Sword className={className} size={s} />;
            case 'Defender': return <Icons.Shield className={className} size={s} />;
            case 'Sniper': return <Icons.Target className={className} size={s} />;
            case 'Caster': return <Icons.Zap className={className} size={s} />;
            case 'Medic': return <Icons.Plus className={className} size={s} />;
            case 'Supporter': return <Icons.BrainCircuit className={className} size={s} />;
            case 'Specialist': return <Icons.Move className={className} size={s} />;
            default: return <Icons.Users className={className} size={s} />;
        }
    },
    RarityStars: ({ r }) => <div className={`flex text-[8px] ${r===7?'text-red-500 drop-shadow-[0_0_2px_rgba(239,68,68,0.8)]':'text-yellow-400'}`}>{[...Array(r)].map((_,i)=><Icons.Star key={i} size={8} fill="currentColor"/>)}</div>
};

const ToastContext = createContext({ show: (msg) => {} });
function ToastProvider({ children }) {
    const [toasts, setToasts] = useState([]);
    const show = (msg) => {
        const id = Date.now() + Math.random();
        setToasts(p => {
            const next = [...p, { id, msg }];
            if (next.length > 3) return next.slice(next.length - 3);
            return next;
        });
        setTimeout(() => setToasts(p => p.filter(t => t.id !== id)), 2500);
    };
    return (
        <ToastContext.Provider value={{ show }}>
            {children}
            <div className="fixed top-8 left-1/2 -translate-x-1/2 z-100 flex flex-col gap-2 pointer-events-none w-full max-w-sm px-4">
                {toasts.map(t => (
                    <div key={t.id} className="holo-panel text-cyan-400 px-4 py-2 clip-tech backdrop-blur font-bold font-mono tracking-wider animate-[slideInRight_0.3s_ease-out] border-l-4 border-l-cyan-500 text-shadow-sm text-sm text-center">
                        {t.msg}
                    </div>
                ))}
            </div>
        </ToastContext.Provider>
    );
}
const useToast = () => useContext(ToastContext);

const MapGrid = memo(({ tiles, onTileClick, selectedOpType, rangePreview }) => {
    return (
        <>
        {tiles.map((r,y) => r.map((c,x) => (
            <div key={`${x}-${y}`} onClick={()=>onTileClick(x,y)} className={`absolute w-16 h-16 border border-white/5 map-grid-cell
            ${c===1?'bg-[#1a1a1a]':c===2?'bg-red-900/20 border-red-500/20':c===3?'bg-blue-900/20 border-blue-500/20':c===4?'bg-orange-500/20 border-orange-500/30':c===9?'bg-black border-dashed border-slate-700':'bg-transparent'}
            ${selectedOpType && ((selectedOpType===OperatorType.RANGED && c===1) || (selectedOpType===OperatorType.MELEE && c===0)) ? 'bg-cyan-500/20 shadow-[inset_0_0_10px_rgba(6,182,212,0.3)]' : ''}
            ${rangePreview && rangePreview.some(p => p.x === x && p.y === y) ? 'bg-orange-500/30 shadow-[inset_0_0_15px_rgba(249,115,22,0.6)]' : ''}
            `} style={{left:x*64, top:y*64}}>
                {c===4 && <div className="w-full h-full flex items-center justify-center text-orange-400/40 font-mono text-xs font-bold">CP</div>}
                {c===2 && <div className="w-full h-full flex items-center justify-center text-red-500/20 font-mono text-xs font-bold">IN</div>}
                {c===3 && <div className="w-full h-full flex items-center justify-center text-blue-500/20 font-mono text-xs font-bold">OUT</div>}
            </div>
        )))}
        </>
    );
});

const RangeGrid = ({ pattern }) => {
      const grid = Array(7).fill(0).map(()=>Array(7).fill(0));
      const center = 3;
      const activePoints = pattern.map(p => ({x: center+p.x, y: center+p.y}));
      return (
          <div className="relative w-28 h-28 bg-slate-900 border border-white/10 mb-4">
              {grid.map((row, y) => row.map((_, x) => {
                  const isActive = activePoints.some(p => p.x === x && p.y === y);
                  const isCenter = x === center && y === center;
                  return (
                      <div key={`${x}-${y}`} className={`absolute w-4 h-4 border border-white/5 
                          ${isCenter ? 'bg-white' : isActive ? 'bg-orange-500' : 'bg-transparent'}`} 
                          style={{left: x*16, top: y*16}}
                      />
                  )
              }))}
          </div>
      );
}

function GameLoop({ levelId, customLevel, squad, onExit, challengeMode }) {
    const isEndless = levelId === 'endless';
    const levelData = useMemo(() => {
        if(customLevel) return customLevel;
        if(isEndless) return { id: 'endless', name: '无尽模式', desc: '抵御无限波次敌人', map: LEVELS[0].map, waves: [] };
        return LEVELS.find(l => l.id === levelId);
    }, [levelId, customLevel]);

    const sortedSquad = useMemo(() => {
          return [...squad].sort((a, b) => {
              const costA = OPERATORS.find(o=>o.id===a.id).baseStats.cost;
              const costB = OPERATORS.find(o=>o.id===b.id).baseStats.cost;
              return costA - costB;
          });
    }, [squad]);

    const toast = useToast();
    const MAX_DEPLOY = 8;
    const [timeSpeed, setTimeSpeed] = useState(1);
    const [paused, setPaused] = useState(false);
    const [enemiesKilled, setEnemiesKilled] = useState(0);
    const [uiDp, setUiDp] = useState(15);
    const [uiLives, setUiLives] = useState(challengeMode ? 1 : 3);
    const [enemies, setEnemies] = useState([]);
    const [deployed, setDeployed] = useState([]);
    const [selectedOp, setSelectedOp] = useState(null);
    const [selectedDeployed, setSelectedDeployed] = useState(null);
    const [cooldowns, setCooldowns] = useState({}); 
    const [deploymentCounter, setDeploymentCounter] = useState(0);
    const [pendingDeploy, setPendingDeploy] = useState(null);
    const [rangePreview, setRangePreview] = useState(null);

    const gameState = useRef({
        dp: 15, lives: challengeMode ? 1 : 3, tiles: JSON.parse(JSON.stringify(levelData.map)), enemies: [], deployed: [],
        wave: { index: 0, timer: 0, spawnCount: 0, state: 'SPAWNING' }, killed: 0, isGameOver: false,
        cooldowns: {}, deploymentCounts: {},
        pitDeath: false, endlessWave: 0
    });
    const lastTimeRef = useRef(0); 
    const requestRef = useRef(0);

    const getBase = (id) => OPERATORS.find(o => o.id === id) || SUMMONS[id];
    const getDeployCost = (opUuid, baseCost) => {
        const count = gameState.current.deploymentCounts[opUuid] || 0;
        if(count === 0) return baseCost;
        if(count === 1) return Math.floor(baseCost * 1.5);
        return Math.floor(baseCost * 2); 
    };

    useEffect(() => {
        if(selectedDeployed) {
            const unit = deployed.find(u => u.id === selectedDeployed);
            if(unit) {
                const base = getBase(unit.operatorId);
                let rp = base.baseStats.rangePattern;
                if(base.isXingyuan && unit.xingyuanPhase >= 2) rp = RANGE_PATTERNS.SURROUND_24;
                setRangePreview(getGridRange(unit, rp || []));
            }
        } else if (!pendingDeploy) {
            setRangePreview(null);
        }
    }, [selectedDeployed, deployed]);

    const handleUnitDeath = (unitId) => {
        const s = gameState.current;
        const unit = s.deployed.find(u => u.id === unitId);
        if (!unit) return;
          
        if (unit.isSummon) {
            s.deployed = s.deployed.filter(u => u.id !== unitId);
            return;
        }

        if (unit.operatorId === 'spec_xuhuan_7') { 
            if (!unit.isReviving) {
                unit.isReviving = true; 
                unit.reviveTimer = 10.0; 
                unit.hp = unit.maxHp * 0.5; 
                toast.show("珝环: 替身激活!");
                return;
            } else {
                  s.deployed = s.deployed.filter(u => u.id !== unitId);
                  if (unit.rosterUuid) s.cooldowns[unit.rosterUuid] = 25;
                  if(selectedDeployed === unitId) setSelectedDeployed(null);
                  return;
            }
        }

        s.deployed = s.deployed.filter(u => u.id !== unitId);
        if (unit.rosterUuid) {
            const base = getBase(unit.operatorId);
            s.cooldowns[unit.rosterUuid] = base.baseStats.redeployTime || 60;
        }
        if(selectedDeployed === unitId) setSelectedDeployed(null);
    };

    const handleRetreat = (unitId) => {
        const s = gameState.current;
        const unit = s.deployed.find(u => u.id === unitId);
        if (!unit) return;
        if (unit.isSummon) {
            s.deployed = s.deployed.filter(u => u.id !== unitId);
            return;
        }
        const base = getBase(unit.operatorId);
        const cost = getDeployCost(unit.rosterUuid, base.baseStats.cost);
        s.dp += Math.floor(cost / 2); 
        s.deployed = s.deployed.filter(u => u.id !== unitId);
        if(unit.rosterUuid) s.cooldowns[unit.rosterUuid] = base.baseStats.redeployTime || 60;
        setUiDp(Math.floor(s.dp));
        if(selectedDeployed === unitId) setSelectedDeployed(null);
    };

    const tick = (delta) => {
        if (paused || gameState.current.isGameOver) return;
        const safeDelta = Math.min(delta, 100);
        const dt = (safeDelta / 1000) * timeSpeed; 
        const s = gameState.current;
          
        if (s.lives <= 0) { 
            s.isGameOver = true; 
            toast.show("作战失败"); 
            setTimeout(() => onExit({ win: false, stats: { killed: s.killed, pitKills: s.pitDeath ? 1 : 0, endlessWave: s.endlessWave } }), 2000); 
            return; 
        }
          
        Object.keys(s.cooldowns).forEach(uuid => {
            s.cooldowns[uuid] -= dt;
            if (s.cooldowns[uuid] <= 0) delete s.cooldowns[uuid];
        });
        setCooldowns({...s.cooldowns});

        let dpRegen = 1.0; 
        s.deployed.forEach(u => {
            const b = getBase(u.operatorId);
            if(!u.isSummon && b.class === 'Vanguard' && !u.isReviving) {
                if (b.id === 'vanguard_myrtle_7') { u.aliveTime = (u.aliveTime || 0) + dt; if(u.aliveTime > 1) dpRegen += 5.0; } 
                else dpRegen += 1.0; 
            }
        });
        s.dp = Math.min(99, s.dp + dt * dpRegen); 
        setUiDp(Math.floor(s.dp));

        const processWave = () => {
            if (isEndless) {
                if (s.wave.state === 'SPAWNING') {
                    if (s.wave.timer >= 2.0 - Math.min(1.5, s.wave.index * 0.1)) { 
                        let spawns = []; 
                        levelData.map.forEach((r, y) => r.forEach((c, x) => { if(c===2) spawns.push({x,y}); }));
                        const sp = spawns[Math.floor(Math.random()*spawns.length)];
                        const types = Object.keys(ENEMY_TYPES);
                        const tier = Math.min(types.length, Math.floor(s.wave.index / 2) + 2);
                        const typeKey = types[Math.floor(Math.random() * tier)];
                        if(sp) {
                            const baseStat = ENEMY_TYPES[typeKey];
                            const buff = challengeMode ? 1.2 : 1.0;
                            s.enemies.push({ 
                                id: uuidv4(), typeId: typeKey, ...baseStat, 
                                maxHp: baseStat.hp * (1 + s.wave.index*0.1) * buff, 
                                atk: baseStat.atk * buff,
                                def: baseStat.def * buff,
                                x: sp.x, y: sp.y, routeIndex: 0, phase: 1 
                            });
                        }
                        s.wave.timer = 0; s.wave.spawnCount++;
                        if(s.wave.spawnCount > 10 + s.wave.index * 2) { s.wave.state = 'RESTING'; s.wave.timer = 0; }
                    } else s.wave.timer += dt;
                } else { if (s.wave.timer >= 5) { s.wave.index++; s.endlessWave = s.wave.index; s.wave.spawnCount = 0; s.wave.state = 'SPAWNING'; } else s.wave.timer += dt; }
            } else {
                if (s.wave.state !== 'DONE') {
                    if (s.wave.index >= levelData.waves.length) s.wave.state = 'DONE';
                    else {
                        const w = levelData.waves[s.wave.index];
                        if (s.wave.state === 'SPAWNING') {
                            if (s.wave.timer >= w.interval) {
                                if (s.wave.spawnCount < w.count) {
                                    let spawns = []; levelData.map.forEach((r, y) => r.forEach((c, x) => { if(c===2) spawns.push({x,y}); }));
                                    const sp = spawns[Math.floor(Math.random()*spawns.length)];
                                    if(sp) {
                                        const baseStat = ENEMY_TYPES[w.type];
                                        const buff = challengeMode ? 1.2 : 1.0;
                                        s.enemies.push({ 
                                            id: uuidv4(), typeId: w.type, ...baseStat, 
                                            maxHp: baseStat.hp * buff,
                                            atk: baseStat.atk * buff,
                                            def: baseStat.def * buff,
                                            x: sp.x, y: sp.y, routeIndex: 0, phase: 1 
                                        });
                                    }
                                    s.wave.spawnCount++; s.wave.timer = 0;
                                } else { s.wave.state = 'RESTING'; s.wave.timer = 0; }
                            } else s.wave.timer += dt;
                        } else if (s.wave.state === 'RESTING') { if (s.wave.timer >= w.rest) { s.wave.index++; s.wave.spawnCount = 0; s.wave.timer = 0; s.wave.state = 'SPAWNING'; } else s.wave.timer += dt; }
                    }
                }
                if (s.wave.state === 'DONE' && s.enemies.length === 0 && !s.isGameOver) {
                    s.isGameOver = true; toast.show("作战胜利"); setTimeout(() => onExit({ win: true, stats: { killed: s.killed, pitKills: s.pitDeath?1:0 }, rewards: { lmd: 2000, orundum: 1000 } }), 1000); 
                }
            }
        };
        processWave();

        s.deployed.forEach(u => { u.blockingCount = 0; });
          
        // Operator Logic
        s.deployed.forEach(u => {
            const base = getBase(u.operatorId);
              
            if (base.isWisadel) {
                const neighbors = [{x:0,y:1},{x:0,y:-1},{x:1,y:0},{x:-1,y:0}];
                const hasSummon = neighbors.some(n => {
                      const nx = Math.round(u.x + n.x);
                      const ny = Math.round(u.y + n.y);
                      return s.deployed.find(d => d.isSummon && d.ownerId === u.id && Math.round(d.x) === nx && Math.round(d.y) === ny);
                });
                u.invisible = hasSummon;
            }

            if (base.isXingyuan) {
                u.xingyuanTimer = (u.xingyuanTimer || 0) + dt;
                if (!u.xingyuanPhase) u.xingyuanPhase = 1;
                if (u.xingyuanTimer >= 15 && u.xingyuanPhase < 4) {
                      u.xingyuanPhase = u.xingyuanPhase + 1;
                      u.xingyuanTimer = 0;
                      const msgs = ["", "星缘祝福了伙伴", "星缘扩展了边界", "星缘降下了惩罚", "白夜将至"];
                      toast.show(msgs[u.xingyuanPhase]);
                }

                const range = u.xingyuanPhase >= 2 ? RANGE_PATTERNS.SURROUND_24 : RANGE_PATTERNS.SURROUND_8;
                  
                if (u.xingyuanPhase >= 1) {
                    s.deployed.forEach(ally => {
                        if (isInRange(u, range, ally.x, ally.y)) {
                            ally.hp = Math.min(getBase(ally.operatorId).baseStats.hp * 2, ally.hp + dt * 10);
                        }
                    });
                }
                  
                if (u.xingyuanPhase === 4) {
                    let totalAllyAtk = 0;
                      s.deployed.forEach(ally => {
                        if (isInRange(u, range, ally.x, ally.y)) totalAllyAtk += getBase(ally.operatorId).baseStats.atk;
                    });
                    u.atkOverride = totalAllyAtk;
                }
            }
        });

        let nextEnemies = [];
        const taunter = s.deployed.find(u => (u.operatorId === 'spec_xuhuan_7' && !u.isReviving) || getBase(u.operatorId).taunt);
        const tauntTarget = taunter ? {x: taunter.x, y: taunter.y} : null;
          
        s.enemies.forEach(e => {
            s.deployed.forEach(u => {
                  const base = getBase(u.operatorId);
                  if (base.isXingyuan && u.xingyuanPhase >= 3) {
                      const range = u.xingyuanPhase >= 2 ? RANGE_PATTERNS.SURROUND_24 : RANGE_PATTERNS.SURROUND_8;
                      if (isInRange(u, range, e.x, e.y)) {
                          e.def = ENEMY_TYPES[e.typeId].def * 0.5;
                          e.atk = ENEMY_TYPES[e.typeId].atk * 0.5;
                      }
                  }
            });

            if(e.poisonStacks > 0) {
                e.poisonTimer = (e.poisonTimer || 0) + dt;
                if(e.poisonTimer >= 1.0) { e.hp -= e.poisonStacks * 150; e.poisonTimer = 0; e.poisonDuration = (e.poisonDuration || 8) - 1; if(e.poisonDuration <= 0) e.poisonStacks = 0; }
            }
              
            if (!e.isFlying && s.tiles[Math.round(e.y)][Math.round(e.x)] === 9) { 
                e.hp = 0; 
                s.pitDeath = true;
            }

            if (e.typeId === 'boss_patriot') {
                  if (e.hp <= 0 && e.phase === 1) { e.phase = 'REVIVING'; e.reviveTimer = 30; e.hp = e.maxHp; toast.show("爱国者：进入重生状态！"); }
                if (e.phase === 'REVIVING') {
                    e.reviveTimer = (e.reviveTimer || 0) - dt;
                    s.deployed.forEach(u => { if(Math.sqrt((e.x-u.x)**2+(e.y-u.y)**2) <= 2) u.hp -= 50 * dt; });
                    if (Math.floor(e.reviveTimer) % 5 === 0 && (e.reviveTimer - Math.floor(e.reviveTimer)) < dt) {
                          let spawns = []; levelData.map.forEach((r, y) => r.forEach((c, x) => { if(c===2) spawns.push({x,y}); }));
                          const sp = spawns[Math.floor(Math.random()*spawns.length)];
                          if(sp) s.enemies.push({ id: uuidv4(), typeId: 'soldier', ...ENEMY_TYPES['soldier'], maxHp: ENEMY_TYPES['soldier'].hp, x: sp.x, y: sp.y, routeIndex: 0, phase: 1 });
                    }
                    if (e.reviveTimer <= 0) { e.phase = 2; e.hp = e.maxHp; toast.show("爱国者：行军姿态 - 进军！"); }
                    nextEnemies.push(e); return; 
                }
                if (e.phase === 2) {
                    e.abilityTimer = (e.abilityTimer || 0) + dt;
                    if (e.abilityTimer >= (challengeMode ? 15 : 25)) {
                        let furthestUnit = null; let maxDist = -1;
                        s.deployed.forEach(u => { 
                            if(u.invisible) return;
                            const dist = Math.sqrt((e.x-u.x)**2 + (e.y-u.y)**2); 
                            if (dist > maxDist && !u.isReviving) { maxDist = dist; furthestUnit = u; } 
                        });
                        if (furthestUnit) { furthestUnit.hp -= 9999; toast.show("爱国者投掷了长枪！"); }
                        e.abilityTimer = 0;
                    }
                }
            }
            if (e.hp <= 0) { if(e.typeId !== 'boss_patriot' || e.phase === 2) { s.killed++; setEnemiesKilled(s.killed); return; } }

            let isBlocked = false;
            let blocker = null;

            if (e.blockedBy) {
                blocker = s.deployed.find(u => u.id === e.blockedBy);
                if (blocker && !blocker.isReviving && blocker.blockingCount <= getBase(blocker.operatorId).baseStats.block) {
                    isBlocked = true;
                    blocker.blockingCount++; 
                } else {
                    e.blockedBy = null; 
                }
            }

            if (isBlocked && blocker) {
                e.attackTimer = (e.attackTimer || 0) + dt;
                const bBase = getBase(blocker.operatorId);
                const blockBonusDef = bBase.isPlatinumDefender ? blocker.blockingCount * 150 : 0;
                const blockBonusRes = bBase.isPlatinumDefender ? blocker.blockingCount * 10 : 0;
                  
                if (e.attackTimer >= e.attackInterval) { 
                    let dmg = e.atk;
                    if(e.typeId === 'caster') dmg = Math.max(dmg*0.05, dmg * (1 - (bBase.baseStats.res + blockBonusRes)/100));
                    else dmg = Math.max(dmg*0.05, dmg - (bBase.baseStats.def + blockBonusDef));
                      
                    blocker.hp -= dmg; 
                    e.attackTimer = 0; 
                      
                    if (bBase.isPlatinumDefender) {
                        blocker.hitCount = (blocker.hitCount || 0) + 1;
                        if (blocker.hitCount >= 5) {
                            s.deployed.forEach(u => { if (Math.abs(u.x - blocker.x) <= 1 && Math.abs(u.y - blocker.y) <= 1) u.hp += bBase.baseStats.hp * 0.8; });
                            blocker.hitCount = 0;
                            toast.show("平板白金: 群体治疗!");
                        }
                    }
                }
            } else if (e.frozen && e.frozen > 0) { e.frozen -= dt;
            } else {
                const path = findPath(Math.round(e.x), Math.round(e.y), s.tiles, s.deployed, tauntTarget, e.isFlying);
                let isAttacking = false;
                if (e.range && e.range > 0) {
                    const validTargets = s.deployed.filter(u => {
                        if (u.invisible && !u.blockedBy) return false; 
                        return Math.sqrt((u.x-e.x)**2+(u.y-e.y)**2) <= e.range;
                    });
                    validTargets.sort((a, b) => {
                        const tauntA = getBase(a.operatorId).taunt ? 1 : 0;
                        const tauntB = getBase(b.operatorId).taunt ? 1 : 0;
                        if(tauntA !== tauntB) return tauntB - tauntA;
                        return b.deployIndex - a.deployIndex;
                    });
                    if (validTargets.length > 0) {
                        isAttacking = true; e.attackTimer = (e.attackTimer || 0) + dt;
                        if (e.attackTimer >= e.attackInterval) {
                            const t = validTargets[0]; 
                            const tBase = getBase(t.operatorId);
                            // Apply block bonus if platinum
                            const tBlockBonusDef = tBase.isPlatinumDefender ? (t.blockingCount||0) * 150 : 0;
                            const tBlockBonusRes = tBase.isPlatinumDefender ? (t.blockingCount||0) * 10 : 0;
                              
                            let dmg = e.atk;
                            if(e.typeId === 'caster') dmg = Math.max(dmg*0.05, dmg * (1 - (tBase.baseStats.res + tBlockBonusRes)/100));
                            else dmg = Math.max(dmg*0.05, dmg - (tBase.baseStats.def + tBlockBonusDef));
                            t.hp -= dmg; e.attackTimer = 0;
                        }
                    }
                }

                if (!isAttacking && path.length > 0) {
                      let next = path[0];
                    if (path.length > 1) {
                          const distToCurrentCenter = Math.sqrt((e.x - path[0].x)**2 + (e.y - path[0].y)**2);
                          if (distToCurrentCenter < 0.05) next = path[1]; 
                    }
                    if (!e.isFlying) {
                        const nextTileX = Math.round(next.x);
                        const nextTileY = Math.round(next.y);
                        const potentialBlocker = s.deployed.find(u => Math.round(u.x) === nextTileX && Math.round(u.y) === nextTileY && !u.isReviving && !u.isSummon);
                        if (potentialBlocker) {
                            const baseOp = getBase(potentialBlocker.operatorId);
                            if (potentialBlocker.blockingCount < baseOp.baseStats.block) {
                                const dist = Math.sqrt((next.x - e.x)**2 + (next.y - e.y)**2);
                                if (dist < 0.8) { isBlocked = true; e.blockedBy = potentialBlocker.id; potentialBlocker.blockingCount++; }
                            }
                        }
                    }
                    if (!isBlocked) {
                        const dx = next.x - e.x; const dy = next.y - e.y; const dist = Math.sqrt(dx*dx+dy*dy); const move = e.speed * dt;
                        if (dist <= move) { e.x = next.x; e.y = next.y; } else { e.x += (dx/dist) * move; e.y += (dy/dist) * move; }
                    }
                }
            }
            if (s.tiles[Math.round(e.y)][Math.round(e.x)] === 3) { s.lives--; setUiLives(s.lives); } else nextEnemies.push(e);
        });
        s.enemies = nextEnemies;

        let nextDeployed = [];
        s.deployed.forEach(u => {
            const base = getBase(u.operatorId);
            if (u.isSummon) {
                if (u.hp <= 0) return;
                if (!s.deployed.some(o => o.id === u.ownerId)) { u.hp = 0; return; } 
                nextDeployed.push(u); return;
            }

            if (base.spType === 'auto') u.sp = Math.min(base.maxSp, (u.sp || 0) + dt);
              
            if (u.isReviving) {
                u.reviveTimer = (u.reviveTimer || 0) - dt;
                s.enemies.forEach(e => { if(Math.sqrt((e.x-u.x)**2+(e.y-u.y)**2) <= 2) { e.hp -= 200 * dt; if(e.hp <= 0 && e.typeId !== 'boss_patriot') { s.killed++; setEnemiesKilled(s.killed); } } });
                if (u.reviveTimer <= 0) { u.isReviving = false; u.hp = base.baseStats.hp; toast.show("珝环: 复活!"); }
                nextDeployed.push(u); return; 
            }
            if (u.hp <= 0) { handleUnitDeath(u.id); if (u.isReviving) nextDeployed.push(u); return; }

            if (u.attackCooldown > 0) u.attackCooldown -= dt;
            else {
                const canHeal = base.class === 'Medic' || base.baseStats.damageType === 3;
                const isXingyuan = base.isXingyuan;

                if (isXingyuan && u.xingyuanPhase < 4) {
                    // Passive mode logic
                } else if (canHeal) {
                      const allies = s.deployed.filter(a => isInRange(u, base.baseStats.rangePattern, a.x, a.y) && a.hp < getBase(a.operatorId).baseStats.hp);
                      if (allies.length > 0) {
                          allies.sort((a,b) => (a.hp/getBase(a.operatorId).baseStats.hp) - (b.hp/getBase(b.operatorId).baseStats.hp));
                          allies[0].hp += base.baseStats.atk;
                          u.attackCooldown = base.baseStats.attackInterval / 60; 
                          if(base.spType === 'attack') u.sp = Math.min(base.maxSp, (u.sp || 0) + 1);
                      }
                } else {
                    let rangePattern = base.baseStats.rangePattern;
                    if (isXingyuan && u.xingyuanPhase >= 2) rangePattern = RANGE_PATTERNS.SURROUND_24;

                    const targets = s.enemies.filter(e => {
                        if (e.invisible && !e.blockedBy) return false;
                        return isInRange(u, rangePattern || [], e.x, e.y)
                    });
                    const validTargets = targets.filter(e => !e.isFlying || base.baseStats.canHitFlying);
                    if (validTargets.length > 0) {
                        const target = validTargets[0];
                          
                        if (base.isWisadel && u.sp >= base.maxSp && !u.skillActive) {
                            u.skillActive = true; u.ammo = 6; toast.show("维什戴尔: 爆裂黎明!");
                            const neighbors = [{x:0,y:1},{x:0,y:-1},{x:1,y:0},{x:-1,y:0}];
                            for (const n of neighbors) {
                                const nx = Math.round(u.x + n.x); const ny = Math.round(u.y + n.y);
                                const tile = s.tiles[ny]?.[nx];
                                const occupied = s.deployed.find(d => Math.round(d.x) === nx && Math.round(d.y) === ny);
                                if ((tile === 0 || tile === 1) && !occupied) {
                                    const summonBase = SUMMONS['spirit_shadow'];
                                    s.deployed.push({ id: uuidv4(), operatorId: 'spirit_shadow', isSummon: true, ownerId: u.id, x: nx, y: ny, direction: Direction.DOWN, hp: summonBase.hp, maxHp: summonBase.hp, blockingCount: 0 });
                                    break; 
                                }
                            }
                        }

                        if (base.isPusher || base.isPuller) {
                            u.pushCount = (u.pushCount || 0) + 1;
                            if (u.pushCount >= 2) {
                                const dx = target.x - u.x; const dy = target.y - u.y; const dist = Math.sqrt(dx*dx + dy*dy);
                                const nx = dx/dist; const ny = dy/dist; const force = base.isPusher ? 2 : -2;
                                target.x += nx * force; target.y += ny * force; u.pushCount = 0;
                            }
                        }
                          
                        let dmg = u.atkOverride || base.baseStats.atk;
                        if (target.hitCount) { target.hp -= 1; }
                        else {
                            if (base.isBluePoison) {
                                dmg *= 4; 
                                s.enemies.forEach(e => { if (Math.abs(e.x - target.x) <= 1.5 && Math.abs(e.y - target.y) <= 1.5) { e.poisonStacks = (e.poisonStacks || 0) + 1; e.poisonDuration = 8; } });
                                target.hp -= Math.max(dmg*0.05, dmg - target.def);
                            } else if (base.isWisadel && u.skillActive) {
                                dmg *= 5; u.ammo -= 1;
                                s.enemies.forEach(e => { if(Math.sqrt((e.x-target.x)**2+(e.y-target.y)**2) <= 2.5) { e.hp -= Math.max(dmg*0.05, dmg - e.def); } });
                                if (u.ammo <= 0) { u.skillActive = false; u.sp = 0; }
                            } else if (base.id === 'supporter_slow') {
                                target.frozen = 2.0; dmg = Math.max(dmg*0.05, dmg * (1 - target.res/100)); target.hp -= dmg;
                            } else {
                                if (base.class === 'Caster' || base.class === 'Supporter' || base.baseStats.damageType === 1) dmg *= (1 - target.res/100); 
                                else if (base.baseStats.damageType === 2) dmg = dmg; 
                                else dmg = Math.max(dmg*0.05, dmg - target.def);
                                target.hp -= dmg;
                            }
                        }
                        u.attackCooldown = base.baseStats.attackInterval / 60; 
                        if(base.spType === 'attack' && !u.skillActive) u.sp = Math.min(base.maxSp, (u.sp || 0) + 1);
                    }
                }
            }
            nextDeployed.push(u);
        });
        s.deployed = nextDeployed;
        setEnemies([...s.enemies]); setDeployed([...s.deployed]);
    };

    useEffect(() => {
        const loop = (t) => { if(lastTimeRef.current) tick(t - lastTimeRef.current); lastTimeRef.current = t; requestRef.current = requestAnimationFrame(loop); };
        requestRef.current = requestAnimationFrame(loop); return () => cancelAnimationFrame(requestRef.current);
    }, [paused, timeSpeed]);

    const handleTileClick = (x, y) => {
        const exists = gameState.current.deployed.find(d => d.x === x && d.y === y);
        if (exists) { setSelectedDeployed(selectedDeployed === exists.id ? null : exists.id); setSelectedOp(null); setPendingDeploy(null); setRangePreview(null); return; }
        if (!selectedOp) return;
        if (gameState.current.deployed.filter(u => !u.isSummon).length >= MAX_DEPLOY) { toast.show("部署位已满"); return; }
        const base = getBase(selectedOp.id);
        const cost = getDeployCost(selectedOp.uuid, base.baseStats.cost);
        if (gameState.current.dp < cost) { toast.show("费用不足"); return; }
        if (gameState.current.cooldowns[selectedOp.uuid] > 0) { toast.show("再部署冷却中"); return; }
        if (gameState.current.deployed.some(d => d.operatorId === selectedOp.id)) { toast.show("干员已部署"); return; }

        const tile = gameState.current.tiles[y][x];
        const isHighGround = tile === 1;
        const isGround = tile === 0 || tile === 4;
        const valid = (base.type === OperatorType.RANGED && isHighGround) || (base.type === OperatorType.MELEE && isGround) || (base.class === 'Specialist' && (isHighGround || isGround)); 
        if (valid) { setPendingDeploy({x, y}); setRangePreview(getGridRange({x, y, direction: Direction.RIGHT}, base.baseStats.rangePattern || [])); } else toast.show("无效的部署位置");
    };

    const confirmDeploy = (dir) => {
        if (!pendingDeploy || !selectedOp) return;
        const base = getBase(selectedOp.id);
        const cost = getDeployCost(selectedOp.uuid, base.baseStats.cost);
        const idx = deploymentCounter + 1; setDeploymentCounter(idx);
        gameState.current.deployed.push({ 
            id: uuidv4(), operatorId: selectedOp.id, rosterUuid: selectedOp.uuid, x: pendingDeploy.x, y: pendingDeploy.y, direction: dir, hp: base.baseStats.hp, maxHp: base.baseStats.hp, sp: 0, attackCooldown: 0, blockingCount: 0, deployIndex: idx, skinId: selectedOp.equippedSkinId
        });
        gameState.current.dp -= cost; 
        gameState.current.deploymentCounts[selectedOp.uuid] = (gameState.current.deploymentCounts[selectedOp.uuid] || 0) + 1;
        setDeployed([...gameState.current.deployed]); setUiDp(Math.floor(gameState.current.dp)); 
        setPendingDeploy(null); setSelectedOp(null); setRangePreview(null);
    };
    const handleDirectionHover = (dir) => { if(pendingDeploy && selectedOp) setRangePreview(getGridRange({x: pendingDeploy.x, y: pendingDeploy.y, direction: dir}, getBase(selectedOp.id).baseStats.rangePattern || [])); };
    const getBorderClass = (rarity) => { if (rarity === 7) return 'border-red-500/80 bg-red-900/40 shadow-[0_0_15px_rgba(239,68,68,0.5)] animate-pulse-red'; if (rarity === 6) return 'border-yellow-400/80 bg-yellow-900/40 shadow-[0_0_10px_rgba(250,204,21,0.4)]'; return 'border-cyan-500/50 bg-slate-900/80'; };

    return (
        <div className="relative h-full w-full flex flex-col items-center justify-center overflow-hidden tech-bg">
            <div className="vignette"></div>
            {challengeMode && <div className="absolute top-12 text-red-600 font-bold font-tech tracking-[1rem] text-2xl animate-pulse z-50">CHALLENGE MODE</div>}
            {isEndless && <div className="absolute top-24 left-1/2 -translate-x-1/2 text-red-500 font-tech text-4xl animate-pulse pointer-events-none z-50 font-bold drop-shadow-[0_0_10px_red]">无尽模式 第 {gameState.current.wave.index} 波</div>}
            <div className="absolute top-4 z-40 flex gap-4 w-full px-4 md:px-8 justify-between pointer-events-none">
                <div className="holo-panel px-4 md:px-8 py-3 clip-tech flex gap-4 md:gap-10 text-white pointer-events-auto mobile-compact items-center">
                    <div className="text-center"><div className="text-[10px] text-red-400 font-bold tracking-widest font-tech">生命</div><div className="text-xl md:text-3xl font-tech font-bold leading-none">{uiLives}</div></div>
                    <div className="w-px bg-white/10 h-8"></div>
                    <div className="text-center"><div className="text-[10px] text-cyan-400 font-bold tracking-widest font-tech">费用</div><div className="text-xl md:text-3xl font-tech font-bold leading-none">{uiDp}</div></div>
                    <div className="w-px bg-white/10 h-8"></div>
                    <div className="text-center"><div className="text-[10px] text-emerald-400 font-bold tracking-widest font-tech">部署</div><div className="text-xl md:text-3xl font-tech font-bold leading-none">{deployed.filter(u=>!u.isSummon).length}/{MAX_DEPLOY}</div></div>
                    <div className="w-px bg-white/10 h-8"></div>
                    <div className="text-center"><div className="text-[10px] text-slate-400 font-bold tracking-widest font-tech">击杀</div><div className="text-xl md:text-3xl font-tech font-bold leading-none">{enemiesKilled}</div></div>
                </div>
                <div className="flex gap-2 pointer-events-auto">
                    <button onClick={()=>setPaused(!paused)} className={`btn-tech w-10 h-10 md:w-14 md:h-14 clip-r ${paused?'text-yellow-400 border-yellow-400':''}`}>{paused?<Icons.Play/>:<Icons.Pause/>}</button>
                    <button onClick={()=>setTimeSpeed(timeSpeed===1?2:1)} className="btn-tech w-10 h-10 md:w-14 md:h-14 clip-r font-tech text-xl">{timeSpeed}x</button>
                    <button onClick={() => onExit({win:false})} className="btn-tech px-4 md:px-6 h-10 md:h-14 clip-r text-red-400 border-red-500/50 hover:shadow-[0_0_10px_red] text-xs md:text-base">撤退</button>
                </div>
            </div>
            <div className="relative border border-cyan-500/20 shadow-[0_0_50px_rgba(6,182,212,0.1)] scale-[0.6] md:scale-90 lg:scale-100 select-none z-10 bg-black/60 backdrop-blur-sm" style={{width: (customLevel?15:12)*64, height: (customLevel?8:6)*64}}>
                <MapGrid tiles={gameState.current.tiles} onTileClick={handleTileClick} selectedOpType={selectedOp ? getBase(selectedOp.id).type : null} rangePreview={rangePreview} />
                {enemies.map(e => (
                    <div key={e.id} className="absolute w-12 h-12 transition-transform duration-100 flex items-center justify-center z-20 will-change-transform" style={{transform: `translate3d(${e.x*64+8}px, ${e.y*64+8}px, 0)`, opacity: (e.invisible && !e.blockedBy) ? 0.2 : 1}}>
                        <div className={`w-full h-full rounded-full border-2 flex items-center justify-center shadow-lg relative ${e.isFlying ? 'border-yellow-400 bg-yellow-900/50 animate-float shadow-[0_20px_10px_rgba(0,0,0,0.5)]' : 'border-red-500 bg-red-900/50'}`}>
                            {e.frozen && e.frozen > 0 && <div className="absolute inset-0 bg-cyan-400/50 rounded-full animate-pulse"></div>}
                            {e.blockedBy && <div className="absolute -left-4 top-0 text-cyan-400"><Icons.Shield size={16} /></div>}
                            {e.poisonStacks > 0 && <div className="absolute -right-2 -top-2 text-purple-500 font-bold text-xs">x{e.poisonStacks}</div>}
                            <span className="text-xs font-bold text-white font-tech">{e.name[0]}</span>
                        </div>
                        <div className="absolute -top-2 w-full h-1 bg-slate-800 border border-white/10"><div className="h-full bg-red-500" style={{width:`${Math.max(0, (e.hp/e.maxHp)*100)}%`}}></div></div>
                    </div>
                ))}
                {deployed.map(u => {
                    const op = getBase(u.operatorId);
                    const isSelected = selectedDeployed === u.id;
                    const skinClass = u.skinId ? 'skin-equipped' : '';
                    const isXingyuan = op.isXingyuan;
                    const blockBonus = op.isPlatinumDefender ? u.blockingCount * 150 : 0;
                    return (
                        <div key={u.id} className={`absolute w-[60px] h-[60px] border-2 z-30 flex items-center justify-center transition-all duration-300 ${getBorderClass(op.rarity)} ${skinClass} ${isSelected ? 'scale-110 ring-2 ring-cyan-400 z-50 bg-black/80' : ''} ${u.isReviving ? 'opacity-50 blur-[1px] animate-float border-dashed' : ''} ${u.invisible ? 'opacity-60' : ''}`} style={{left:u.x*64+2, top:u.y*64+2}} onClick={(e) => { e.stopPropagation(); setSelectedDeployed(isSelected ? null : u.id); setSelectedOp(null); setPendingDeploy(null); }}>
                            <div className={`opacity-90 ${op.rarity===7?'text-red-400':op.rarity===6?'text-yellow-400':'text-cyan-200'}`} style={{transform: `rotate(${u.direction === Direction.UP ? -90 : u.direction === Direction.DOWN ? 90 : u.direction === Direction.LEFT ? 180 : 0}deg)`}}>
                                <Icons.ClassIcon cls={op.class} size={32} />
                                <div className="absolute top-1/2 left-full -translate-y-1/2 -translate-x-1 w-0 h-0 border-l-8 border-l-current border-y-[5px] border-y-transparent"></div>
                            </div>
                            <div className="absolute -bottom-3 w-[90%] h-1 bg-slate-800 border border-white/10"><div className="h-full bg-emerald-500" style={{width:`${(u.hp/op.baseStats.hp)*100}%`}}></div></div>
                            {op.maxSp && <div className="absolute -bottom-5 w-[90%] h-1 bg-slate-800 border border-white/10"><div className="h-full bg-orange-500" style={{width:`${(u.sp/op.maxSp)*100}%`}}></div></div>}
                            {u.ammo > 0 && <div className="absolute top-0 right-0 bg-black text-[10px] text-white px-1 border border-white/20">{u.ammo}</div>}
                            {u.isReviving && <div className="absolute inset-0 flex items-center justify-center font-mono font-bold text-red-500 text-xl bg-black/50">{Math.ceil(u.reviveTimer || 0)}</div>}
                            {isXingyuan && <div className="absolute -top-2 left-0 w-full text-[8px] text-center text-cyan-300 bg-black/50">PHASE {u.xingyuanPhase}</div>}
                            {op.isPlatinumDefender && u.blockingCount > 0 && <div className="absolute top-0 left-0 bg-blue-600 text-[10px] px-1">+{blockBonus} DEF</div>}
                            {isSelected && <div className="absolute -top-12 bg-red-600 hover:bg-red-500 text-white text-xs font-bold px-4 py-2 clip-tech cursor-pointer shadow-[0_0_15px_red] flex items-center gap-2 z-60" onClick={(e) => { e.stopPropagation(); handleRetreat(u.id); setSelectedDeployed(null); setRangePreview(null); }}><Icons.LogOut size={14} /> 撤退</div>}
                        </div>
                    )
                })}
                {pendingDeploy && (
                    <div className="absolute z-50 flex items-center justify-center" style={{left: pendingDeploy.x*64, top: pendingDeploy.y*64, width:64, height:64}}>
                        <div className="absolute -top-16 flex flex-col items-center"><div className="animate-bounce text-cyan-400 font-bold text-xs mb-1 font-mono bg-black/50 px-2">选择方向</div><button onMouseEnter={()=>handleDirectionHover(Direction.UP)} onClick={()=>confirmDeploy(Direction.UP)} className="p-2 bg-slate-900 hover:bg-cyan-500 border border-cyan-400 shadow-[0_0_10px_cyan] text-cyan-400 hover:text-black"><Icons.ArrowUp size={20}/></button></div>
                        <button onMouseEnter={()=>handleDirectionHover(Direction.RIGHT)} onClick={()=>confirmDeploy(Direction.RIGHT)} className="absolute -right-16 p-2 bg-slate-900 hover:bg-cyan-500 border border-cyan-400 shadow-[0_0_10px_cyan] rotate-90 text-cyan-400 hover:text-black"><Icons.ArrowUp size={20}/></button>
                        <button onMouseEnter={()=>handleDirectionHover(Direction.DOWN)} onClick={()=>confirmDeploy(Direction.DOWN)} className="absolute -bottom-16 p-2 bg-slate-900 hover:bg-cyan-500 border border-cyan-400 shadow-[0_0_10px_cyan] rotate-180 text-cyan-400 hover:text-black"><Icons.ArrowUp size={20}/></button>
                        <button onMouseEnter={()=>handleDirectionHover(Direction.LEFT)} onClick={()=>confirmDeploy(Direction.LEFT)} className="absolute -left-16 p-2 bg-slate-900 hover:bg-cyan-500 border border-cyan-400 shadow-[0_0_10px_cyan] -rotate-90 text-cyan-400 hover:text-black"><Icons.ArrowUp size={20}/></button>
                        <div className="w-16 h-16 bg-cyan-500/30 border-2 border-cyan-400 animate-pulse"></div>
                    </div>
                )}
            </div>
            <div className="absolute bottom-0 w-full overflow-x-auto flex gap-2 md:gap-4 p-4 md:p-6 holo-panel rounded-t-lg z-30 custom-scroll">
                {sortedSquad.map((op) => {
                    const base = getBase(op.id);
                    const deployedInst = deployed.find(d=>d.operatorId===op.id);
                    const currentCost = getDeployCost(op.uuid, base.baseStats.cost);
                    const cd = cooldowns[op.uuid] || 0;
                    const can = uiDp>=currentCost && !deployedInst && deployed.filter(u=>!u.isSummon).length < MAX_DEPLOY && cd <= 0;
                    const isSel = selectedOp?.uuid === op.uuid;
                    const hasSkin = op.equippedSkinId;
                    return (
                        <button key={op.uuid} onClick={()=>{ if(can) { setSelectedOp(op); setSelectedDeployed(null); setPendingDeploy(null); }}} disabled={!can && !deployedInst && cd<=0} 
                        className={`shrink-0 w-20 h-24 md:w-24 md:h-32 border relative transition-all flex flex-col items-center justify-between p-2 group overflow-hidden clip-r
                            ${isSel ? 'bg-cyan-900/40 border-cyan-400 -translate-y-2 shadow-[0_0_20px_rgba(6,182,212,0.3)]' : can ? 'bg-slate-900/60 border-white/10 hover:border-cyan-500/50 hover:bg-slate-800' : 'bg-slate-950 border-white/5 opacity-60 grayscale'}
                            ${hasSkin ? 'skin-equipped' : ''}
                        `}>
                            <div className={`w-full flex justify-between items-start text-xs font-mono font-bold ${base.rarity===6?'text-yellow-400':base.rarity===7?'text-red-500':'text-slate-400'}`}>
                                <Icons.ClassIcon cls={base.class} size={12}/>
                                <span>{currentCost}</span>
                            </div>
                            <div className={`font-bold text-xl font-tech ${base.rarity===7?'text-red-500 drop-shadow-md':base.rarity===6?'text-yellow-400':'text-white'}`}>{base.name.substring(0,2)}</div>
                            <div className="w-full h-1 bg-white/10 overflow-hidden"><div className={`h-full ${can?'bg-cyan-400 shadow-[0_0_5px_cyan]':'bg-red-500'}`}></div></div>
                            {deployedInst && <div className="absolute inset-0 bg-black/60 flex items-center justify-center backdrop-blur-[2px] border border-cyan-500/30"><Icons.Check className="text-cyan-400" size={40}/></div>}
                            {cd > 0 && (<div className="absolute inset-0 bg-black/80 flex flex-col items-center justify-center z-10"><Icons.Clock size={24} className="text-slate-400 mb-2"/><span className="text-2xl font-mono font-bold text-white">{Math.ceil(cd)}</span></div>)}
                        </button>
                    )
                })}
            </div>
        </div>
    )
}

function MainMenu({ gameState, roster, onSelectLevel, onNavigate, onSetAssistant }) {
      const assistant = useMemo(() => {
          // Safety check: Ensure assistant exists in roster, fallback to first
          const found = roster.find(o => o.uuid === gameState.assistantUuid);
          return found || roster[0];
      }, [gameState.assistantUuid, roster]);
      
      const [showAssistantSelect, setShowAssistantSelect] = useState(false);
      const base = OPERATORS.find(o => o.id === assistant.id) || OPERATORS[0];
      
      return (
          <div className="w-full h-full flex cyber-bg overflow-hidden relative text-white">
              <div className="hex-overlay"></div><div className="scanline"></div>
              <div className="absolute top-0 left-0 w-full h-16 md:h-20 flex justify-between items-center px-4 md:px-10 z-20">
                  <div className="font-tech text-2xl md:text-4xl font-bold tracking-widest text-cyan-400 drop-shadow-[0_0_10px_rgba(6,182,212,0.8)]">PRTS <span className="text-xs md:text-sm text-slate-500 ml-2">V6.8</span></div>
                  <div className="flex gap-2 md:gap-8 font-mono text-[10px] md:text-sm mobile-hide">
                      <div className="text-cyan-400">源石: {gameState.resources.originium}</div>
                      <div className="text-red-400">合成玉: {gameState.resources.orundum}</div>
                      <div className="text-yellow-400">龙门币: {gameState.resources.lmd}</div>
                      <div className="text-slate-300">理智: {gameState.resources.sanity}/120</div>
                  </div>
              </div>
              <div className="w-full md:w-1/2 h-full relative z-10 flex items-end pb-20 pl-4 md:pl-20 pointer-events-none md:pointer-events-auto">
                  <div className="absolute bottom-0 left-0 h-[90%] w-full opacity-80 bg-linear-to-t from-black via-transparent to-transparent z-10"></div>
                  <Icons.ClassIcon cls={base.class} className={`w-[300px] h-[300px] md:w-[600px] md:h-[600px] opacity-10 absolute -left-10 bottom-0 ${base.rarity===7?'text-red-500':base.rarity===6?'text-yellow-500':'text-white'}`}/>
                  <div className="relative z-20 mb-10 pointer-events-auto">
                      <h1 className="text-4xl md:text-6xl font-black font-tech mb-2 tracking-tighter">{assistant.name}</h1>
                      <button onClick={()=>setShowAssistantSelect(true)} className="text-xs bg-white/10 px-2 py-1 hover:bg-cyan-500/20 border border-white/20 rounded">更换助理</button>
                  </div>
              </div>
              <div className="absolute top-16 right-0 md:static md:w-1/2 h-full flex items-center justify-center p-4 md:p-20 z-10 pt-10 md:pt-32 w-[85%] md:w-[60%]">
                  <div className="grid grid-cols-2 md:grid-cols-4 gap-2 md:gap-4 w-full mobile-grid">
                      <button onClick={onSelectLevel} className="col-span-2 row-span-2 aspect-2/1 md:aspect-square holo-panel clip-tech hover:bg-cyan-900/20 transition-all group flex flex-col p-4 md:p-8 justify-between border-cyan-500/30 z-30 mobile-menu-btn">
                          <Icons.Map size={32} className="text-cyan-400 group-hover:scale-110 transition-transform md:scale-150"/>
                          <div><div className="text-2xl md:text-4xl font-bold font-tech text-cyan-400">作战</div><div className="text-[10px] text-slate-400">TERMINAL</div></div>
                      </button>
                      <button onClick={()=>onNavigate(13)} className="aspect-square holo-panel hover:bg-green-900/20 flex flex-col items-center justify-center gap-2 border-green-500/30 z-30 mobile-menu-btn"><Icons.Users size={24} className="text-green-400"/><span className="font-bold text-sm md:text-base">编队</span></button>
                      <button onClick={()=>onNavigate(3)} className="aspect-square holo-panel hover:bg-yellow-900/20 flex flex-col items-center justify-center gap-2 border-yellow-500/30 z-30 mobile-menu-btn"><Icons.Database size={24} className="text-yellow-400"/><span className="font-bold text-sm md:text-base">寻访</span></button>
                      <button onClick={()=>onNavigate(4)} className="aspect-square holo-panel hover:bg-lime-900/20 flex flex-col items-center justify-center gap-2 border-lime-500/30 z-30 mobile-menu-btn"><Icons.Hammer size={24} className="text-lime-400"/><span className="font-bold text-sm md:text-base">基建</span></button>
                      <button onClick={()=>onNavigate(7)} className="aspect-square holo-panel hover:bg-orange-900/20 flex flex-col items-center justify-center gap-2 border-orange-500/30 z-30 mobile-menu-btn"><Icons.ShoppingCart size={24} className="text-orange-400"/><span className="font-bold text-sm md:text-base">采购</span></button>
                      
                      <button onClick={()=>onNavigate(5)} className="col-span-2 holo-panel hover:bg-purple-900/20 flex items-center px-4 gap-4 border-purple-500/30 z-30 h-16 md:h-auto group"><Icons.Users size={28} className="text-purple-400 group-hover:scale-110 transition-transform"/><span className="font-bold text-lg md:text-xl">干员</span></button>
                      
                      <button onClick={()=>onNavigate(12)} className="col-span-1 holo-panel hover:bg-blue-900/20 flex flex-col items-center justify-center border-blue-500/30 z-30 h-20 md:h-auto"><Icons.Check size={28} className="text-blue-400 mb-1"/><span className="text-xs text-blue-400/80">任务</span></button>
                      <button onClick={()=>onNavigate(10)} className="col-span-1 holo-panel hover:bg-amber-900/20 flex flex-col items-center justify-center border-amber-500/30 z-30 h-20 md:h-auto"><Icons.Trophy size={28} className="text-amber-400 mb-1"/><span className="text-xs text-amber-400/80">荣誉</span></button>
                      
                      <button onClick={()=>onNavigate(9)} className="col-span-1 holo-panel hover:bg-rose-900/20 flex flex-col items-center justify-center border-rose-500/30 z-30 h-20 md:h-auto"><Icons.Edit size={28} className="text-rose-400 mb-1"/><span className="text-xs text-rose-400/80">编辑</span></button>
                      <button onClick={()=>onNavigate(8)} className="col-span-1 holo-panel hover:bg-indigo-900/20 flex flex-col items-center justify-center border-indigo-500/30 z-30 h-20 md:h-auto"><Icons.Book size={28} className="text-indigo-400 mb-1"/><span className="text-xs text-indigo-400/80">图鉴</span></button>

                      <button onClick={()=>onNavigate(11)} className="col-span-2 holo-panel hover:bg-blue-900/20 flex items-center justify-center gap-2 border-blue-500/30 z-30 h-16 md:h-auto border-t-2 border-t-blue-500/50"><Icons.Ticket size={24} className="text-blue-400"/><span className="font-bold text-lg">公开招募</span></button>
                  </div>
              </div>
              {showAssistantSelect && (
                <div className="fixed inset-0 z-50 bg-black/90 backdrop-blur-md flex flex-col p-8 animate-[fadeIn_0.2s]">
                      <div className="flex justify-between items-center mb-4 pb-4 border-b border-white/10">
                          <h2 className="text-2xl font-bold font-tech tracking-widest text-white">选择助理干员</h2>
                          <button onClick={()=>setShowAssistantSelect(false)} className="p-2 hover:bg-white/10 rounded-full"><Icons.X/></button>
                      </div>
                      <div className="flex-1 overflow-y-auto grid grid-cols-3 md:grid-cols-8 gap-4 custom-scroll">
                          {roster.map(op => {
                              const b = OPERATORS.find(o=>o.id===op.id);
                              return (
                                  <button key={op.uuid} onClick={()=>{onSetAssistant(op.uuid); setShowAssistantSelect(false);}} 
                                    className={`aspect-3/4 flex flex-col items-center justify-center gap-2 border border-white/10 hover:bg-white/5 hover:border-cyan-400 transition-all clip-r relative group
                                        ${gameState.assistantUuid===op.uuid ? 'bg-cyan-900/30 border-cyan-400' : ''}
                                        rarity-${b.rarity}
                                    `}>
                                      <div className={`text-4xl font-black opacity-30 group-hover:opacity-100 transition-all ${b.rarity===7?'text-red-500':b.rarity===6?'text-yellow-500':'text-slate-500'}`}>{b.name.substring(0,1)}</div>
                                      <div className="font-bold z-10 text-xs md:text-base">{b.name}</div>
                                      <Icons.RarityStars r={b.rarity}/>
                                  </button>
                              )
                          })}
                      </div>
                </div>
            )}
          </div>
      )
}
  
const LevelSelect = ({ completed, customMaps, onSelect, onBack, challengeMode, setChallengeMode, onDeleteCustom }) => { 
    const [tab, setTab] = useState('MAIN'); 
    const list = tab === 'MAIN' ? LEVELS.filter(l => !l.id.startsWith('C-') && l.id !== 'CE-5') : tab === 'RESOURCE' ? [...LEVELS.filter(l => l.id === 'CE-5'), { id: 'endless', name: '无尽模式', desc: '抵御无限波次敌人', map: LEVELS[0].map, waves: [] }] : customMaps; 
    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-8 gap-4 border-b border-white/10 bg-black/40 backdrop-blur">
                <button onClick={onBack}><Icons.ArrowLeft/></button>
                <h1 className="text-2xl font-bold font-tech">作战任务</h1>
                <div className="ml-12 flex gap-2">
                    <button onClick={()=>setTab('MAIN')} className={`px-6 py-1 clip-tech font-bold ${tab==='MAIN'?'bg-cyan-600':'bg-slate-800'}`}>主线</button>
                    <button onClick={()=>setTab('RESOURCE')} className={`px-6 py-1 clip-tech font-bold ${tab==='RESOURCE'?'bg-cyan-600':'bg-slate-800'}`}>资源</button>
                    <button onClick={()=>setTab('CUSTOM')} className={`px-6 py-1 clip-tech font-bold ${tab==='CUSTOM'?'bg-orange-600':'bg-slate-800'}`}>自定义</button>
                </div>
                {tab === 'MAIN' && (
                    <div className="ml-auto flex items-center gap-2">
                        <span className={`text-sm font-bold ${challengeMode ? 'text-red-500' : 'text-slate-500'}`}>突袭模式</span>
                        <button onClick={() => setChallengeMode(!challengeMode)} className={`w-12 h-6 rounded-full border transition-colors relative ${challengeMode ? 'bg-red-900/50 border-red-500' : 'bg-slate-800 border-slate-600'}`}>
                            <div className={`absolute top-1 w-4 h-4 rounded-full transition-all ${challengeMode ? 'right-1 bg-red-500' : 'left-1 bg-slate-500'}`}></div>
                        </button>
                    </div>
                )}
            </div>
            <div className="p-12 grid grid-cols-1 md:grid-cols-3 gap-8 overflow-y-auto">
                {list.map((lvl,idx)=>(
                    <div key={lvl.id} className="relative group">
                        <button onClick={()=>onSelect(lvl.id)} className="w-full h-40 holo-panel clip-tech p-6 text-left hover:border-cyan-400 group relative">
                            <div className="text-4xl font-black opacity-10 absolute right-0 top-0">{idx+1}</div>
                            <div className="text-2xl font-bold group-hover:text-cyan-400">{lvl.name}</div>
                            <div className="text-sm text-slate-400 mt-2">{lvl.desc}</div>
                            {completed.includes(lvl.id) && <Icons.Check className="absolute bottom-4 right-4 text-green-500"/>}
                        </button>
                        {tab === 'CUSTOM' && (
                            <button onClick={(e)=>{e.stopPropagation(); onDeleteCustom(lvl.id)}} className="absolute top-2 right-2 p-2 bg-red-900/80 text-red-400 hover:text-white rounded z-20">
                                <Icons.Trash size={16}/>
                            </button>
                        )}
                    </div>
                ))}
            </div>
        </div>
    );
};
  
const SquadSelect = ({ roster, initialSquadIds, onStart, onBack, onSavePreset, onLoadPreset, editMode }) => { 
    const sortedRoster = useMemo(() => {
        return [...roster].sort((a, b) => {
            const opA = OPERATORS.find(o => o.id === a.id);
            const opB = OPERATORS.find(o => o.id === b.id);
            if (opA.rarity !== opB.rarity) return opB.rarity - opA.rarity;
            return b.level - a.level;
        });
    }, [roster]);

    const [squad, setSquad] = useState(() => { if (!initialSquadIds) return []; return roster.filter(r => initialSquadIds.includes(r.uuid)); }); 
    const toggleOp = (op) => { if (squad.find(s => s.uuid === op.uuid)) setSquad(squad.filter(s => s.uuid !== op.uuid)); else if (squad.length < 12) setSquad([...squad, op]); };
    const handleSave = (slot) => { onSavePreset(slot, squad.map(o=>o.uuid)); alert(`预设 ${slot} 已保存`); };
    const handleLoad = (slot) => { const ids = onLoadPreset(slot); if(ids) setSquad(roster.filter(r=>ids.includes(r.uuid))); };

    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-4 md:px-8 border-b border-white/10 bg-black/60">
                <button onClick={onBack} className="mr-4"><Icons.ArrowLeft/></button>
                <h1 className="text-lg md:text-xl font-bold font-tech">编队选择 {squad.length}/12</h1>
                <div className="flex gap-2 ml-4 md:ml-8">
                    {[1,2,3].map(i => (
                        <div key={i} className="flex gap-1 items-center border border-white/10 p-1 bg-slate-900">
                            <span className="text-[10px] md:text-xs text-slate-500 px-1">#{i}</span>
                            <button onClick={()=>handleSave(i)} className="p-1 hover:text-cyan-400" title="保存"><Icons.Save size={16}/></button>
                            <button onClick={()=>handleLoad(i)} className="p-1 hover:text-cyan-400" title="加载"><Icons.Download size={16}/></button>
                        </div>
                    ))}
                </div>
                {editMode ? (
                    <button onClick={onBack} className="ml-auto btn-tech px-4 md:px-8 py-2 clip-tech bg-green-500/20 border-green-500 text-green-400">保存配置</button>
                ) : (
                    <button onClick={() => squad.length>0 && onStart(squad)} className="ml-auto btn-tech px-4 md:px-8 py-2 clip-tech">开始行动</button>
                )}
            </div>
            <div className="flex-1 flex overflow-hidden">
                <div className="flex-1 p-4 md:p-8 overflow-y-auto grid grid-cols-3 md:grid-cols-6 gap-2 md:gap-4 content-start custom-scroll">
                    {sortedRoster.map(op => { 
                        const base = OPERATORS.find(o=>o.id===op.id); 
                        const sel = squad.find(s=>s.uuid===op.uuid); 
                        const hasSkin = op.equippedSkinId;
                        return (
                            <button key={op.uuid} onClick={()=>toggleOp(op)} className={`aspect-3/4 clip-r border flex flex-col items-center justify-center gap-2 relative rarity-${base.rarity} ${hasSkin?'skin-equipped':''} ${sel?'border-cyan-400 ring-2 ring-cyan-500/50':'border-white/10'}`}>
                                <div className="font-bold text-xs md:text-base">{base.name}</div>
                                <Icons.ClassIcon cls={base.class} />
                                {sel && <div className="absolute inset-0 bg-cyan-500/20 flex items-center justify-center"><Icons.Check className="text-cyan-400"/></div>}
                            </button>
                        )
                    })}
                </div>
            </div>
        </div>
    );
};

const Base = ({ base, roster, onAssign, onCollect, onBack }) => { 
    const [accum, setAccum] = useState({lmd:0, orundum:0}); 
    const [showAssign, setShowAssign] = useState(null); 
      
    const safeBase = useMemo(() => {
        const safe = { ...base, assigned: { ...(base.assigned || {}) } };
        if (!safe.assigned.trade) safe.assigned.trade = [];
        if (!safe.assigned.factory) safe.assigned.factory = [];
        return safe;
    }, [base]);

    const efficiency = {
        trade: 1 + (safeBase.assigned.trade.length || 0) * 0.2,
        factory: 1 + (safeBase.assigned.factory.length || 0) * 0.2
    };

    useEffect(() => { 
        const i = setInterval(() => { 
            const min = Math.floor((Date.now()-safeBase.lastCollected)/60000); 
            setAccum({
                lmd: Math.floor(min * 500 * safeBase.level * efficiency.trade), 
                orundum: Math.floor(min * 50 * safeBase.level * efficiency.factory)
            }); 
        }, 1000); 
        return () => clearInterval(i); 
    }, [safeBase, efficiency]); 

    const getOpName = (uuid) => {
        if(!uuid) return '???';
        // Safe check for missing roster data
        const r = roster.find(o=>o.uuid===uuid);
        return r ? OPERATORS.find(op=>op.id===r.id).name : '未知干员';
    };

    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-8 border-b border-white/10"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold font-tech ml-4">基建系统</h1></div>
            <div className="flex-1 flex flex-col md:flex-row items-center justify-center gap-8 p-8 overflow-y-auto">
                <div className="w-full md:w-96 holo-panel p-6 flex flex-col justify-between relative">
                    <div className="flex justify-between items-center mb-4">
                        <h2 className="text-2xl font-bold text-blue-400">贸易站</h2>
                        <div className="text-xs text-slate-400">效率: {(efficiency.trade*100).toFixed(0)}%</div>
                    </div>
                    <div className="text-4xl font-mono mb-6">{accum.lmd} 龙门币</div>
                    <div className="space-y-2 mb-4">
                        <div className="text-xs text-slate-500">进驻干员 (点击更换)</div>
                        <div className="flex gap-2">
                            {[0,1].map(i => (
                                <button key={i} onClick={()=>setShowAssign({type:'trade', slot:i})} className="w-12 h-12 border border-white/20 bg-black flex items-center justify-center hover:border-blue-400">
                                    {safeBase.assigned.trade[i] ? <div className="text-[10px]">{getOpName(safeBase.assigned.trade[i]).slice(0,2)}</div> : <Icons.Plus size={16} className="text-slate-600"/>}
                                </button>
                            ))}
                        </div>
                    </div>
                </div>
                <div className="w-full md:w-96 holo-panel p-6 flex flex-col justify-between">
                    <div className="flex justify-between items-center mb-4">
                        <h2 className="text-2xl font-bold text-yellow-400">制造站</h2>
                        <div className="text-xs text-slate-400">效率: {(efficiency.factory*100).toFixed(0)}%</div>
                    </div>
                    <div className="text-4xl font-mono mb-6">{accum.orundum} 合成玉</div>
                      <div className="space-y-2 mb-4">
                        <div className="text-xs text-slate-500">进驻干员 (点击更换)</div>
                        <div className="flex gap-2">
                            {[0,1].map(i => (
                                <button key={i} onClick={()=>setShowAssign({type:'factory', slot:i})} className="w-12 h-12 border border-white/20 bg-black flex items-center justify-center hover:border-yellow-400">
                                    {safeBase.assigned.factory[i] ? <div className="text-[10px]">{getOpName(safeBase.assigned.factory[i]).slice(0,2)}</div> : <Icons.Plus size={16} className="text-slate-600"/>}
                                </button>
                            ))}
                        </div>
                    </div>
                </div>
            </div>
            <div className="h-24 flex justify-center items-center p-4 bg-black/40">
                <button onClick={onCollect} className="btn-tech px-12 py-4 clip-tech text-xl w-full md:w-auto"><Icons.Zap/> 收取资源</button>
            </div>
              
            {showAssign && (
                <div className="fixed inset-0 z-50 bg-black/90 flex items-center justify-center p-8">
                    <div className="w-full max-w-lg bg-slate-900 border border-cyan-500/30 p-6 rounded max-h-[80vh] flex flex-col">
                        <h3 className="text-xl font-bold mb-4">选择进驻干员</h3>
                        <div className="flex-1 overflow-y-auto grid grid-cols-4 gap-4 custom-scroll">
                            <button onClick={()=>{onAssign(showAssign.type, showAssign.slot, null); setShowAssign(null);}} className="aspect-square border border-red-500/50 text-red-500 flex items-center justify-center">撤下</button>
                            {roster.map(op => {
                                const b = OPERATORS.find(o=>o.id===op.id);
                                // Safety Check if operator definition is missing
                                if (!b) return null;
                                const tradeArr = safeBase.assigned.trade;
                                const factoryArr = safeBase.assigned.factory;
                                const assigned = (tradeArr.includes(op.uuid) || factoryArr.includes(op.uuid));
                                return (
                                    <button key={op.uuid} disabled={assigned} onClick={()=>{onAssign(showAssign.type, showAssign.slot, op.uuid); setShowAssign(null);}} 
                                        className={`aspect-square border flex flex-col items-center justify-center text-xs ${assigned ? 'border-slate-700 opacity-30' : 'border-white/20 hover:border-cyan-400'}`}>
                                        {b.name}
                                    </button>
                                )
                            })}
                        </div>
                        <button onClick={()=>setShowAssign(null)} className="mt-4 w-full py-2 bg-slate-800">取消</button>
                    </div>
                </div>
            )}
        </div>
    );
};

const Tasks = ({ tasks, onClaim, onBack }) => (
    <div className="w-full h-full flex flex-col cyber-bg text-white">
        <div className="h-20 flex items-center px-8 border-b border-white/10"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold font-tech ml-4">任务系统</h1></div>
        <div className="p-8 grid grid-cols-1 gap-4 overflow-y-auto max-w-4xl mx-auto w-full">
            {tasks.map(t => {
                const def = TASKS.find(def => def.id === t.id);
                const completed = t.progress >= def.target;
                return (
                    <div key={t.id} className="holo-panel p-6 flex items-center justify-between">
                        <div>
                            <h3 className="text-xl font-bold text-cyan-400">{def.title}</h3>
                            <p className="text-slate-400 text-sm">{def.desc}</p>
                            <div className="mt-2 text-xs font-mono text-slate-500">进度: {t.progress}/{def.target}</div>
                            <div className="w-48 h-1 bg-slate-800 mt-1"><div className="h-full bg-cyan-500" style={{width: `${Math.min(100, (t.progress/def.target)*100)}%`}}></div></div>
                        </div>
                        <div className="flex items-center gap-8">
                            <div className="text-right text-sm text-yellow-400 font-mono">
                                {def.reward.lmd && <div>龙门币 x{def.reward.lmd}</div>}
                                {def.reward.orundum && <div>合成玉 x{def.reward.orundum}</div>}
                                {def.reward.recruitTicket && <div>招聘许可 x{def.reward.recruitTicket}</div>}
                            </div>
                            <button onClick={()=>onClaim(t.id)} disabled={!completed || t.claimed} className={`px-6 py-2 border ${t.claimed ? 'border-slate-600 text-slate-600' : completed ? 'border-yellow-500 bg-yellow-900/30 text-yellow-400 animate-pulse' : 'border-slate-600 text-slate-500'}`}>
                                {t.claimed ? '已领取' : completed ? '领取奖励' : '进行中'}
                            </button>
                        </div>
                    </div>
                )
            })}
        </div>
    </div>
);

const Recruitment = ({ resources, onRecruit, onBack }) => {
    const [selectedTags, setSelectedTags] = useState([]);
    const [result, setResult] = useState(null);
      
    const toggleTag = (t) => {
        if(selectedTags.includes(t)) setSelectedTags(selectedTags.filter(tag=>tag!==t));
        else if(selectedTags.length < 3) setSelectedTags([...selectedTags, t]);
    };

    const handleRecruit = () => {
        if(resources.lmd < 1000 || resources.recruitTicket < 1) return;
        const op = onRecruit(selectedTags, 9);
        setResult(op);
    };

    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-8 border-b border-white/10"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold font-tech ml-4">公开招募</h1></div>
            {result ? (
                <div className="flex-1 flex flex-col items-center justify-center animate-[fadeIn_0.3s]">
                    <div className="text-2xl mb-8 font-bold">招募成功</div>
                    <div className={`w-48 h-64 border-2 flex flex-col items-center justify-center gap-4 rarity-${result.rarity} mb-8`}>
                        <div className="text-2xl font-bold">{result.name}</div>
                        <Icons.RarityStars r={result.rarity}/>
                        <Icons.ClassIcon cls={result.class} size={32}/>
                    </div>
                    <button onClick={()=>{setResult(null); setSelectedTags([]);}} className="btn-tech px-8 py-2">确认</button>
                </div>
            ) : (
                <div className="flex-1 p-8 flex flex-col items-center">
                    <div className="w-full max-w-4xl">
                        <div className="mb-8">
                            <h3 className="text-cyan-400 font-bold mb-4">选择标签 (最多3个)</h3>
                            <div className="flex flex-wrap gap-4">
                                {TAGS.map(t => (
                                    <button key={t} onClick={()=>toggleTag(t)} className={`px-4 py-2 border transition-all ${selectedTags.includes(t) ? 'bg-cyan-900/50 border-cyan-400 text-white' : 'bg-black border-slate-700 text-slate-400 hover:border-slate-400'}`}>
                                        {t}
                                    </button>
                                ))}
                            </div>
                        </div>
                        <div className="flex items-center justify-between bg-slate-900/50 p-6 border border-white/10">
                            <div className="flex gap-8 text-sm font-mono text-slate-300">
                                <div>消耗龙门币: <span className={resources.lmd<1000?'text-red-500':'text-white'}>1000</span></div>
                                <div>消耗许可: <span className={(resources.recruitTicket || 0)<1?'text-red-500':'text-white'}>{(resources.recruitTicket || 0)}/1</span></div>
                                <div>招募时长: 09:00:00</div>
                            </div>
                            <button onClick={handleRecruit} disabled={resources.lmd<1000 || (resources.recruitTicket || 0)<1} className="btn-tech px-12 py-4">开始招募</button>
                        </div>
                    </div>
                </div>
            )}
        </div>
    );
};

const Shop = ({ resources, onBuy, onExchange, onBack, onBuySkin, ownedSkins }) => {
    const toast = useToast();
    const [tab, setTab] = useState('ORIGINIUM');

    const buy = (amount) => { onBuy(amount); toast.show(`充值成功: 获得 ${amount} 源石`); }
    const exchange = (type) => { if(resources.originium < 1) { toast.show("源石不足"); return; } onExchange(type); toast.show("兑换成功"); }
    const buySkin = (skinId, cost) => {
        if(resources.originium < cost) { toast.show("源石不足"); return; }
        if(ownedSkins.includes(skinId)) { toast.show("已拥有该时装"); return; }
        onBuySkin(skinId, cost);
        toast.show("时装获取成功");
    }

    return (
        <div className="w-full h-full flex flex-col tech-bg text-white">
            <div className="h-24 flex items-center px-10 gap-6 border-b border-white/10 bg-black/60 backdrop-blur z-30">
                    <button onClick={onBack} className="p-3 rounded-full hover:bg-white/10 transition-colors"><Icons.ArrowLeft size={28}/></button>
                    <div className="flex flex-col">
                        <h1 className="text-4xl font-bold font-tech tracking-widest text-yellow-400 drop-shadow-[0_0_10px_rgba(234,179,8,0.5)]">采购中心</h1>
                        <div className="text-xs text-slate-400 tracking-[0.5em]">STORE</div>
                    </div>
                    <div className="ml-auto flex gap-8 items-center">
                        <div className="flex items-center gap-3 bg-slate-900/80 px-6 py-2 rounded-full border border-yellow-500/30"><Icons.Gem className="text-yellow-400" size={20}/> <span className="font-mono text-xl font-bold">{resources.originium}</span></div>
                        <div className="flex items-center gap-3 bg-slate-900/80 px-6 py-2 rounded-full border border-red-500/30"><Icons.Database className="text-red-400" size={20}/> <span className="font-mono text-xl font-bold">{resources.orundum}</span></div>
                    </div>
            </div>

            <div className="flex flex-1 overflow-hidden">
                <div className="w-64 bg-slate-900/90 border-r border-white/10 flex flex-col py-10 z-20">
                    <button onClick={()=>setTab('ORIGINIUM')} className={`px-10 py-6 text-left font-tech text-xl tracking-wider transition-all border-l-4 ${tab==='ORIGINIUM'?'border-yellow-400 bg-yellow-900/20 text-yellow-400':'border-transparent text-slate-500 hover:text-slate-200 hover:bg-white/5'}`}>源石交易所</button>
                    <button onClick={()=>setTab('EXCHANGE')} className={`px-10 py-6 text-left font-tech text-xl tracking-wider transition-all border-l-4 ${tab==='EXCHANGE'?'border-cyan-400 bg-cyan-900/20 text-cyan-400':'border-transparent text-slate-500 hover:text-slate-200 hover:bg-white/5'}`}>凭证交易所</button>
                    <button onClick={()=>setTab('SKINS')} className={`px-10 py-6 text-left font-tech text-xl tracking-wider transition-all border-l-4 ${tab==='SKINS'?'border-pink-400 bg-pink-900/20 text-pink-400':'border-transparent text-slate-500 hover:text-slate-200 hover:bg-white/5'}`}>时装商店</button>
                </div>

                <div className="flex-1 p-10 overflow-y-auto custom-scroll relative">
                    <div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/diagmonds-light.png')] opacity-5 pointer-events-none"></div>
                      
                    {tab === 'ORIGINIUM' && (
                        <div className="grid grid-cols-3 gap-8 animate-[fadeIn_0.3s]">
                            {[1, 6, 20, 50, 100, 600].map(amt => (
                                <button key={amt} onClick={()=>buy(amt)} className="aspect-video relative group border border-yellow-500/30 bg-yellow-900/10 hover:bg-yellow-500/20 transition-all rounded-xl overflow-hidden flex flex-col items-center justify-center hover:scale-[1.02] hover:shadow-[0_0_30px_rgba(234,179,8,0.2)]">
                                    <div className="absolute top-0 left-0 w-full h-1 bg-linear-to-r from-transparent via-yellow-400 to-transparent opacity-50"></div>
                                    <Icons.Gem size={64} className="text-yellow-400 mb-4 group-hover:animate-pulse filter drop-shadow-[0_0_10px_rgba(234,179,8,0.5)]"/>
                                    <div className="text-5xl font-bold text-white font-tech mb-2">{amt}</div>
                                    <div className="text-sm text-yellow-500/80 font-mono tracking-widest">源石</div>
                                </button>
                            ))}
                        </div>
                    )}

                    {tab === 'EXCHANGE' && (
                        <div className="flex flex-col gap-6 animate-[fadeIn_0.3s] max-w-4xl mx-auto">
                            <div className="bg-slate-800/80 p-8 rounded-xl border border-cyan-500/30 flex items-center justify-between shadow-lg">
                                <div className="flex items-center gap-6"><div className="p-4 bg-cyan-900/30 rounded-full border border-cyan-400/50"><Icons.Zap size={40} className="text-cyan-400"/></div><div><h3 className="text-2xl font-bold text-white">理智补给包</h3><p className="text-slate-400 mt-1">应急理智合剂 x10 (1200 理智)</p></div></div>
                                <button onClick={()=>exchange('sanity')} className="btn-tech px-10 py-4 clip-tech text-xl flex items-center gap-3 hover:bg-cyan-500 hover:text-black"><Icons.Gem size={20}/> <span>1</span> <span className="opacity-50">|</span> <span>兑换</span></button>
                            </div>
                            <div className="bg-slate-800/80 p-8 rounded-xl border border-red-500/30 flex items-center justify-between shadow-lg">
                                <div className="flex items-center gap-6"><div className="p-4 bg-red-900/30 rounded-full border border-red-400/50"><Icons.Database size={40} className="text-red-400"/></div><div><h3 className="text-2xl font-bold text-white">合成玉补给</h3><p className="text-slate-400 mt-1">合成玉 x2000</p></div></div>
                                <button onClick={()=>exchange('orundum')} className="btn-tech px-10 py-4 clip-tech text-xl flex items-center gap-3 border-red-500 text-red-400 hover:bg-red-500 hover:text-black hover:shadow-[0_0_20px_red]"><Icons.Gem size={20}/> <span>1</span> <span className="opacity-50">|</span> <span>兑换</span></button>
                            </div>
                        </div>
                    )}

                    {tab === 'SKINS' && (
                        <div className="grid grid-cols-3 gap-6 animate-[fadeIn_0.3s]">
                            {SKINS.map(skin => {
                                const owned = ownedSkins.includes(skin.id);
                                return (
                                    <div key={skin.id} className={`relative aspect-3/4 border-2 ${owned ? 'border-slate-600 bg-slate-800/50' : 'border-pink-500/50 bg-pink-900/10 hover:border-pink-400 hover:bg-pink-900/30'} transition-all rounded-lg overflow-hidden group flex flex-col`}>
                                        <div className="flex-1 flex items-center justify-center relative">
                                            <div className="absolute inset-0 bg-linear-to-t from-black to-transparent opacity-80"></div>
                                            <div className="text-6xl font-black opacity-20 text-white group-hover:scale-110 transition-transform duration-500">{skin.name[0]}</div>
                                            {owned && <div className="absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm"><Icons.Check size={64} className="text-green-400"/></div>}
                                        </div>
                                        <div className="p-4 bg-black/60 backdrop-blur border-t border-white/10">
                                            <div className="text-xs text-pink-400 font-bold tracking-widest mb-1">OUTFIT</div>
                                            <div className="text-xl font-bold text-white">{skin.name}</div>
                                            <div className="text-xs text-slate-400 mb-4">干员: {skin.operatorId}</div>
                                              
                                            {owned ? (
                                                <button disabled className="w-full py-2 bg-slate-700 text-slate-400 font-mono font-bold cursor-not-allowed">已拥有</button>
                                            ) : (
                                                <button onClick={()=>buySkin(skin.id, skin.cost)} className="w-full py-2 bg-pink-600 hover:bg-pink-500 text-white font-mono font-bold flex items-center justify-center gap-2 transition-colors">
                                                    <Icons.Gem size={16}/> {skin.cost}
                                                </button>
                                            )}
                                        </div>
                                    </div>
                                )
                            })}
                        </div>
                    )}
                </div>
            </div>
        </div>
    )
};

const Gacha = ({ resources, pity, onPull, onBack }) => { 
    const [res, setRes] = useState(null); 
    const [anim, setAnim] = useState(false); 
    const [bagColor, setBagColor] = useState('white');

    const pull = (ten) => { 
        if(anim) return; 
        const results = onPull(ten); 
        if(results) { 
            const maxRarity = Math.max(...results.map(o => o.rarity));
            setBagColor(maxRarity >= 6 ? 'rainbow' : maxRarity === 5 ? 'yellow' : maxRarity === 4 ? 'blue' : 'white');
            setAnim(true); 
            setTimeout(()=>{
                setRes(results);
                setAnim(false);
            }, 2500); 
        }
    }; 
      
    return (
        <div className="w-full h-full flex flex-col tech-bg text-white overflow-hidden relative">
            <div className="h-20 px-8 flex items-center border-b border-white/10 z-20 bg-black/40"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold ml-4">干员寻访</h1></div>
            {anim ? (
                <div className="absolute inset-0 z-50 bg-black flex items-center justify-center">
                    <div className="relative w-full h-full flex flex-col items-center justify-center">
                        <div className={`w-2 h-full absolute bg-white/10`}></div>
                        <div className={`w-16 h-32 bg-slate-800 border-2 rounded z-10 animate-bounce relative ${bagColor === 'rainbow' ? 'border-red-500 shadow-[0_0_50px_red]' : bagColor === 'yellow' ? 'border-yellow-400 shadow-[0_0_30px_yellow]' : 'border-white'}`}>
                            <div className="absolute inset-0 flex items-center justify-center text-xs font-mono">PRTS</div>
                        </div>
                        <div className="absolute top-1/2 w-full h-1 bg-white animate-[flashWhite_0.2s_2s_forwards]"></div>
                    </div>
                </div>
            ) : res ? (
                <div className="absolute inset-0 z-40 bg-black/95 flex flex-col items-center justify-center p-10 animate-[fadeIn_0.5s]">
                    <div className="grid grid-cols-5 gap-4">
                        {res.map((o,i) => (
                            <div key={i} className={`w-32 h-48 border-2 flex flex-col items-center justify-center gap-2 bg-slate-900 relative animate-[fadeIn_0.5s_${i*0.1}s_both] rarity-${o.rarity}`}>
                                <div className="font-bold text-center px-2">{o.name}</div>
                                <Icons.RarityStars r={o.rarity}/>
                                <Icons.ClassIcon cls={o.class} size={24}/>
                                {o.isNew && <div className="absolute top-0 right-0 bg-red-600 text-[10px] px-1">NEW</div>}
                            </div>
                        ))}
                    </div>
                    <button onClick={()=>setRes(null)} className="mt-8 btn-tech px-8 py-2">确认</button>
                </div>
            ) : (
                <div className="flex-1 flex items-center justify-center gap-8 relative">
                      <div className="absolute inset-0 bg-[url('https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExb3Z6cW84Zzh5aHBsZ2Z6ZHM4Z3Z6Zzh5aHBsZ2Z6ZHM4Z3Z6ZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3o7TKMt1VVNkHVyPaE/giphy.gif')] opacity-10 bg-cover grayscale"></div>
                      <div className="z-10 flex gap-8">
                          <button onClick={()=>pull(false)} className="btn-tech w-64 h-32 flex flex-col gap-2">
                              <div className="text-xl">寻访一次</div>
                              <div className="text-sm text-red-400 flex items-center gap-1"><Icons.Database size={14}/> 600</div>
                          </button>
                          <button onClick={()=>pull(true)} className="btn-tech w-64 h-32 border-yellow-500 text-yellow-500 flex flex-col gap-2 shadow-[0_0_20px_rgba(234,179,8,0.3)]">
                              <div className="text-xl">寻访十次</div>
                              <div className="text-sm text-red-400 flex items-center gap-1"><Icons.Database size={14}/> 6000</div>
                              <div className="text-[10px] bg-yellow-900/50 px-2 rounded">必定获得4星以上干员</div>
                          </button>
                      </div>
                      <div className="absolute bottom-10 text-slate-500 font-mono text-sm">
                          距离下一次必出6星还剩: <span className="text-white">{50 - pity}</span> 抽 (保底机制)
                      </div>
                </div>
            )}
        </div>
    )
};

const Archive = ({ roster, onBack }) => { 
    const [tab, setTab] = useState('OP'); 
    const ownedIds = roster.map(r => r.id); 
    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-8 border-b border-white/10"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold font-tech ml-4">档案资料</h1><div className="ml-8 flex gap-2"><button onClick={()=>setTab('OP')} className={`px-4 py-1 ${tab==='OP'?'bg-cyan-600':'bg-slate-800'}`}>干员</button><button onClick={()=>setTab('ENEMY')} className={`px-4 py-1 ${tab==='ENEMY'?'bg-cyan-600':'bg-slate-800'}`}>敌人</button></div></div>
            <div className="flex-1 overflow-y-auto p-8 custom-scroll">
                <div className="grid grid-cols-4 gap-4">
                    {tab==='OP' ? OPERATORS.map(op => {
                        if(op.isXingyuan && !ownedIds.includes(op.id)) return null;
                        return (
                            <div key={op.id} className={`p-4 holo-panel ${ownedIds.includes(op.id)?'rarity-'+op.rarity:'opacity-50 grayscale'}`}>
                                <div className="font-bold">{op.name}</div>
                                <Icons.RarityStars r={op.rarity}/>
                                <div className="text-xs text-slate-400 mt-2">{op.description}</div>
                                <div className="text-xs text-cyan-400 mt-1">{op.skill || '无技能'}</div>
                            </div>
                        )
                    }) : Object.values(ENEMY_TYPES).map((e,i)=>(
                        <div key={i} className="p-4 holo-panel">
                            <div className="font-bold text-red-400">{e.name}</div>
                            <div className="text-xs text-slate-300 mt-2">{e.description}</div>
                            <div className="text-xs text-slate-500 mt-2 font-mono">HP: {e.hp} | ATK: {e.atk}</div>
                        </div>
                    ))}
                </div>
            </div>
        </div>
    ); 
};
  
const Achievements = ({ state, onBack }) => { 
    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-8 border-b border-white/10"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold font-tech ml-4">荣誉室</h1></div>
            <div className="p-8 grid grid-cols-3 gap-4 overflow-y-auto custom-scroll">
                {ACHIEVEMENTS.map(a=>{ 
                    const u = state.achievements.includes(a.id);
                    if (a.hidden && !u) return (
                        <div key={a.id} className="p-6 holo-panel border-2 border-slate-800 opacity-30">
                            <h3 className="font-bold text-xl">???</h3>
                            <p className="text-sm text-slate-500">隐藏成就</p>
                        </div>
                    );
                    return (
                        <div key={a.id} className={`p-6 holo-panel border-2 ${u?'border-yellow-500/50 bg-yellow-900/10':'border-slate-800 opacity-50 grayscale'}`}>
                            <div className="flex justify-between items-center mb-2">
                                <h3 className={`font-bold text-xl ${u?'text-yellow-400':'text-slate-500'}`}>{a.title}</h3>
                                {u && <Icons.Trophy className="text-yellow-400"/>}
                            </div>
                            <p className="text-sm text-slate-300">{a.desc}</p>
                        </div>
                    )
                })}
            </div>
        </div>
    ); 
};

function MapEditor({ onSave, onBack }) {
    const toast = useToast();
    const W = 15, H = 8;
    const [name, setName] = useState("自定义行动");
    const [map, setMap] = useState(Array(H).fill(0).map(()=>Array(W).fill(0)));
    const [brush, setBrush] = useState(0); 
    const [waves, setWaves] = useState([]);
    const [importCode, setImportCode] = useState("");
    const [showImport, setShowImport] = useState(false);
    const [selectedEnemy, setSelectedEnemy] = useState('soldier');
    const [waveCount, setWaveCount] = useState(5);
    const [waveInterval, setWaveInterval] = useState(2);

    const handleTileClick = (x, y) => { const newMap = [...map]; newMap[y] = [...newMap[y]]; newMap[y][x] = brush; setMap(newMap); };
    const addWave = () => { setWaves([...waves, { type: selectedEnemy, count: waveCount, interval: waveInterval, rest: 5 }]); };
    const removeWave = (idx) => { setWaves(waves.filter((_, i) => i !== idx)); };

    const save = () => {
        const hasSpawn = map.some(row => row.includes(2));
        const hasBase = map.some(row => row.includes(3));
        if(!hasSpawn || !hasBase) { toast.show("错误: 地图必须包含 红门(2) 和 蓝门(3)"); return; }
        if(waves.length === 0) { toast.show("错误: 至少配置一波敌人"); return; }
        onSave({ id: `C-${Date.now().toString().slice(-4)}`, name, desc: "玩家自制关卡", map, waves, isCustom: true });
        toast.show("地图已保存");
    };

    const exportMap = () => {
        const hasSpawn = map.some(row => row.includes(2));
        const hasBase = map.some(row => row.includes(3));
        if(!hasSpawn || !hasBase) { toast.show("无法导出: 地图不完整"); return; }
        const code = encodeMapData({ name, map, waves });
        if(code) { navigator.clipboard.writeText(code); toast.show("分享码已复制到剪贴板!"); }
    };

    const handleImport = () => {
        if(!importCode) return;
        const data = decodeMapData(importCode);
        if(data) { setName(data.name); setMap(data.map); setWaves(data.waves); toast.show("地图导入成功!"); setShowImport(false); } 
        else { toast.show("导入失败: 无效的分享码"); }
    }

    const tiles = [
        { id: 0, name: '地面', color: 'bg-[#111]' },
        { id: 1, name: '高台', color: 'bg-[#333]' },
        { id: 2, name: '红门', color: 'bg-red-900/50 border-red-500' },
        { id: 3, name: '蓝门', color: 'bg-blue-900/50 border-blue-500' },
        { id: 4, name: '路径点', color: 'bg-orange-500/50 border-orange-500' },
        { id: 9, name: '深坑/障碍', color: 'bg-black border-dashed border-gray-600' }
    ];

    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white relative">
            {showImport && (
                <div className="absolute inset-0 z-50 bg-black/90 flex items-center justify-center p-20">
                    <div className="bg-slate-900 border border-cyan-500/50 p-8 w-full max-w-2xl rounded shadow-2xl">
                        <h2 className="text-2xl font-bold mb-4 text-cyan-400">导入地图</h2>
                        <textarea className="w-full h-32 bg-black border border-white/20 p-4 font-mono text-xs mb-4 focus:border-cyan-400 outline-none" placeholder="在此粘贴分享码..." value={importCode} onChange={e => setImportCode(e.target.value)}/>
                        <div className="flex gap-4 justify-end"><button onClick={()=>setShowImport(false)} className="px-6 py-2 text-slate-400 hover:text-white">取消</button><button onClick={handleImport} className="px-6 py-2 bg-cyan-600 text-white font-bold rounded hover:bg-cyan-500">解析导入</button></div>
                    </div>
                </div>
            )}
            <div className="h-20 flex items-center px-8 gap-4 border-b border-white/10 bg-black/40 backdrop-blur relative z-20">
                <button onClick={onBack} className="p-2 rounded hover:bg-white/10"><Icons.ArrowLeft/></button>
                <h1 className="text-3xl font-bold font-tech tracking-widest text-orange-400 drop-shadow-[0_0_10px_rgba(251,146,60,0.5)]">地图编辑器</h1>
                <input value={name} onChange={e=>setName(e.target.value)} className="bg-slate-900 border border-white/20 px-4 py-2 ml-8 rounded font-bold font-mono text-cyan-400 w-64 focus:border-cyan-400 outline-none" placeholder="地图名称"/>
                <div className="ml-auto flex gap-4"><button onClick={()=>setShowImport(true)} className="btn-tech px-4 py-2 text-xs border-white/20 text-white/70 hover:text-white"><Icons.Download size={16} className="mr-2"/> 导入地图</button><button onClick={exportMap} className="btn-tech px-4 py-2 text-xs border-cyan-500/30 text-cyan-400 hover:shadow-[0_0_10px_cyan]"><Icons.Share size={16} className="mr-2"/> 导出分享码</button><button onClick={save} className="btn-tech px-8 py-2 clip-tech flex gap-2 items-center bg-cyan-600/20 border-cyan-500 hover:bg-cyan-600"><Icons.Check/> 保存</button></div>
            </div>
            <div className="flex-1 flex overflow-hidden relative z-10">
                <div className="w-64 holo-panel p-6 flex flex-col gap-4 overflow-y-auto custom-scroll border-r border-white/10">
                    <h3 className="font-bold text-cyan-400 uppercase tracking-wider font-tech text-lg">地形画笔</h3>
                    {tiles.map(t => (<button key={t.id} onClick={()=>setBrush(t.id)} className={`flex items-center gap-4 p-3 border transition-all text-left clip-r ${brush === t.id ? 'border-cyan-400 bg-cyan-900/30 shadow-[0_0_10px_rgba(6,182,212,0.2)]' : 'border-white/10 hover:bg-white/5'}`}><div className={`w-8 h-8 border ${t.color}`}></div><span className="text-sm font-mono">{t.name}</span></button>))}
                </div>
                <div className="flex-1 flex items-center justify-center bg-[#020202] overflow-auto p-8 relative">
                    <div className="hex-overlay"></div>
                    <div className="relative border border-cyan-900 shadow-[0_0_30px_rgba(6,182,212,0.1)]" style={{width: W*64, height: H*64}}>
                        {map.map((row, y) => row.map((cell, x) => (
                            <div key={`${x}-${y}`} onMouseDown={()=>handleTileClick(x,y)} onMouseEnter={(e)=>{ if(e.buttons===1) handleTileClick(x,y) }} className={`absolute w-16 h-16 border border-white/5 cursor-pointer hover:border-white/50 ${cell===1?'bg-[#222]':cell===2?'bg-red-900/50 border-red-500/30':cell===3?'bg-blue-900/50 border-blue-500/30':cell===4?'bg-purple-500/40 border-purple-400':cell===9?'bg-black': 'bg-transparent'}`} style={{left: x*64, top: y*64}}>
                                {cell===2 && <div className="absolute inset-0 flex items-center justify-center text-red-500 font-bold">IN</div>}
                                {cell===3 && <div className="absolute inset-0 flex items-center justify-center text-blue-500 font-bold">OUT</div>}
                                {cell===4 && <div className="absolute inset-0 flex items-center justify-center text-purple-300 font-bold">CP</div>}
                            </div>
                        )))}
                    </div>
                </div>
                <div className="w-80 holo-panel p-6 flex flex-col gap-4 overflow-y-auto custom-scroll border-l border-white/10">
                    <h3 className="font-bold text-cyan-400 uppercase tracking-wider font-tech text-lg">波次配置</h3>
                    <div className="p-4 border border-white/10 bg-black/40">
                        <div className="mb-2 text-xs text-slate-400 uppercase">敌人类型</div>
                        <select className="w-full bg-slate-900 border border-white/20 p-2 mb-3 font-mono text-sm" value={selectedEnemy} onChange={e=>setSelectedEnemy(e.target.value)}>{Object.keys(ENEMY_TYPES).map(k => <option key={k} value={k}>{ENEMY_TYPES[k].name}</option>)}</select>
                        <div className="flex gap-2 mb-4"><div className="flex-1"><div className="text-xs text-slate-500 mb-1">数量</div><input type="number" className="w-full bg-slate-900 border border-white/20 p-1 text-center font-mono" value={waveCount} onChange={e=>setWaveCount(Number(e.target.value))}/></div><div className="flex-1"><div className="text-xs text-slate-500 mb-1">间隔(秒)</div><input type="number" className="w-full bg-slate-900 border border-white/20 p-1 text-center font-mono" value={waveInterval} onChange={e=>setWaveInterval(Number(e.target.value))}/></div></div>
                        <button onClick={addWave} className="w-full py-2 bg-cyan-900/30 border border-cyan-500/50 hover:bg-cyan-500/20 text-cyan-400 font-bold text-sm clip-r">+ 添加波次</button>
                    </div>
                    <div className="flex flex-col gap-2 mt-2">{waves.map((w, i) => (<div key={i} className="flex justify-between items-center p-3 bg-slate-800/50 border border-white/5"><div><div className="font-bold text-sm text-slate-200">第 {i+1} 波: <span className="text-cyan-300">{ENEMY_TYPES[w.type].name}</span></div><div className="text-xs text-slate-500 font-mono">数量: {w.count} | 间隔: {w.interval}s</div></div><button onClick={()=>removeWave(i)} className="text-slate-500 hover:text-red-400"><Icons.Trash size={14}/></button></div>))}</div>
                </div>
            </div>
        </div>
    );
}
  
const Roster = ({ roster, resources, ownedSkins, onUpgrade, onEquipSkin, onBack }) => { 
    const [sel, setSel] = useState(null); 
    const op = sel ? roster.find(o=>o.uuid===sel) : null; 
    const base = op ? OPERATORS.find(o=>o.id===op.id) : null; 
    const StatBar = ({ label, val, max, color }) => (<div className="mb-1"><div className="flex justify-between text-xs text-slate-400 mb-0.5"><span>{label}</span><span>{val}</span></div><div className="w-full h-1 bg-slate-800"><div className={`h-full ${color}`} style={{width:`${Math.min(100, (val/max)*100)}%`}}></div></div></div>);
    const DamageTypeLabel = ({type}) => { const types = ['物理', '法术', '真实', '治疗']; const colors = ['text-slate-300', 'text-purple-400', 'text-white', 'text-green-400']; return <span className={colors[type]}>{types[type]}</span>; }
    const mySkins = useMemo(() => { if(!op) return []; return SKINS.filter(s => s.operatorId === op.id && ownedSkins.includes(s.id)); }, [op, ownedSkins]);

    return (
        <div className="w-full h-full flex flex-col cyber-bg text-white">
            <div className="h-20 flex items-center px-8 border-b border-white/10"><button onClick={onBack}><Icons.ArrowLeft/></button><h1 className="text-2xl font-bold font-tech ml-4">干员管理</h1></div>
            <div className="flex-1 flex overflow-hidden flex-col md:flex-row">
                <div className="flex-1 p-4 md:p-8 grid grid-cols-4 md:grid-cols-6 gap-2 md:gap-4 content-start overflow-y-auto custom-scroll">
                    {roster.map(r=>{ const b = OPERATORS.find(o=>o.id===r.id); const hasSkin = r.equippedSkinId; return (<button key={r.uuid} onClick={()=>setSel(r.uuid)} className={`aspect-3/4 border flex flex-col items-center justify-center rarity-${b.rarity} ${hasSkin?'skin-equipped':''} ${sel===r.uuid?'border-cyan-400 ring-2 ring-cyan-500/50':''}`}>{b.name}</button>) })}
                </div>
                <div className="w-full md:w-96 holo-panel p-6 md:p-8 border-t md:border-t-0 md:border-l border-white/10 overflow-y-auto custom-scroll h-[50vh] md:h-auto">
                    {op && base ? (
                        <div>
                            <div className="flex justify-between items-start mb-6"><div><h2 className="text-2xl md:text-3xl font-bold">{base.name}</h2><div className="text-sm text-slate-400 mt-1">{base.class} - {base.type}</div></div><Icons.RarityStars r={base.rarity}/></div>
                            <div className="flex justify-center mb-6"><RangeGrid pattern={base.baseStats.rangePattern || []} /></div>
                            <div className="grid grid-cols-2 gap-4 mb-6 text-xs font-mono text-slate-300"><div>部署费用: <span className="text-white">{base.baseStats.cost}</span></div><div>阻挡数: <span className="text-white">{base.baseStats.block}</span></div><div>再部署: <span className="text-white">{base.baseStats.redeployTime}s</span></div><div>攻击间隔: <span className="text-white">{(base.baseStats.attackInterval/60).toFixed(1)}s</span></div><div>伤害类型: <DamageTypeLabel type={base.baseStats.damageType || 0}/></div></div>
                            <div className="flex flex-wrap gap-2 mb-6">{base.tags.map(t => <span key={t} className="bg-slate-800 px-2 py-1 text-[10px] border border-slate-600 text-slate-400">{t}</span>)}</div>
                            <div className="space-y-4 mb-8"><StatBar label="生命上限" val={base.baseStats.hp + (op.level-1)*20} max={5000} color="bg-green-500"/><StatBar label="攻击力" val={base.baseStats.atk + (op.level-1)*5} max={1200} color="bg-red-500"/><StatBar label="防御力" val={base.baseStats.def + (op.level-1)*3} max={1000} color="bg-blue-500"/><StatBar label="法术抗性" val={base.baseStats.res} max={100} color="bg-purple-500"/></div>
                            <div className="flex justify-between items-center mb-4"><div className="font-bold text-xl">Lv.{op.level}</div><div className="text-sm text-slate-400">潜能 {op.potential}/6</div></div>
                            <div className="grid grid-cols-3 gap-2 mb-6">
                                <button onClick={()=>{if(resources.lmd>=1000)onUpgrade(sel, 1)}} className="btn-tech py-2 text-xs">升级 x1</button>
                                <button onClick={()=>{if(resources.lmd>=5000)onUpgrade(sel, 5)}} className="btn-tech py-2 text-xs">升级 x5</button>
                                <button onClick={()=>{if(resources.lmd>=10000)onUpgrade(sel, 10)}} className="btn-tech py-2 text-xs">升级 x10</button>
                            </div>
                            <div className="border-t border-white/10 pt-4"><div className="text-sm font-bold text-pink-400 mb-2">时装更换</div><div className="grid grid-cols-3 gap-2"><button onClick={()=>onEquipSkin(op.uuid, null)} className={`p-2 text-xs border ${!op.equippedSkinId ? 'border-pink-500 bg-pink-900/20' : 'border-white/10'}`}>默认</button>{mySkins.map(s => (<button key={s.id} onClick={()=>onEquipSkin(op.uuid, s.id)} className={`p-2 text-xs border ${op.equippedSkinId===s.id ? 'border-pink-500 bg-pink-900/20' : 'border-white/10'}`}>{s.name}</button>))}</div>{mySkins.length === 0 && <div className="text-xs text-slate-500 mt-2">暂无时装，请前往商店购买</div>}</div>
                        </div>
                    ) : <div className="text-center mt-20 text-slate-500">请选择干员</div>}
                </div>
            </div>
        </div>
    );
};

// Main App Component
function App() {
    const toast = useToast();
    const [view, setView] = useState(0); 
    const [selectedLvl, setSelectedLvl] = useState(null);
    const [currentSquad, setCurrentSquad] = useState([]);
    const [challengeMode, setChallengeMode] = useState(false);
      
    const [gameState, setGameState] = useState(() => {
        const def = { 
            resources: {lmd:100000, orundum:60000, sanity:120, originium: 0, recruitTicket: 50}, 
            roster: [{...OPERATORS[3], uuid:'1', level:1, potential:1}], 
            base: {lastCollected:Date.now(), level:1, assigned: { trade: [], factory: [] } },
            completedLevels: [],
            assistantUuid: '1',
            lastSquadIds: [],
            squadPresets: { 1: [], 2: [], 3: [] },
            pityCounter: 0,
            customMaps: [],
            achievements: [],
            ownedSkins: [],
            tasks: TASKS.map(t => ({ id: t.id, progress: 0, claimed: false })),
            stats: { totalKills: 0, totalWins: 0, pitKills: 0, customClears: 0, maxEndlessWave: 0, luckyPull: false },
            recruitmentCount: 0
        };
        try { 
            const saved = JSON.parse(localStorage.getItem('save_redux_cyber_cn') || 'null');
            if (!saved) return def;
              
            // Deep Merge Logic to prevent crash from missing keys in old saves
            return {
                ...def,
                ...saved,
                resources: { ...def.resources, ...(saved.resources || {}) },
                base: { ...def.base, ...(saved.base || {}), assigned: { ...def.base.assigned, ...(saved.base?.assigned || {}) } },
                stats: { ...def.stats, ...(saved.stats || {}) },
                // Ensure Tasks are migrated if new tasks added
                tasks: TASKS.map(t => {
                    const existing = saved.tasks?.find(st => st.id === t.id);
                    return existing || { id: t.id, progress: 0, claimed: false };
                })
            };
        } catch { return def; }
    });
      
    useEffect(() => localStorage.setItem('save_redux_cyber_cn', JSON.stringify(gameState)), [gameState]);

    useEffect(() => {
        let updated = false;
        const nextAchievements = [...gameState.achievements];
        ACHIEVEMENTS.forEach(a => { if (!nextAchievements.includes(a.id) && a.condition(gameState)) { nextAchievements.push(a.id); toast.show(`解锁成就: ${a.title}`); updated = true; } });
        const nextTasks = [...gameState.tasks];
        const loginTaskIdx = nextTasks.findIndex(t => t.id === 'daily_login');
        if (loginTaskIdx >= 0 && nextTasks[loginTaskIdx].progress < 1) { nextTasks[loginTaskIdx] = { ...nextTasks[loginTaskIdx], progress: 1 }; updated = true; }
        if (updated) { setGameState(p => ({ ...p, achievements: nextAchievements, tasks: nextTasks })); }
    }, [gameState.stats, gameState.roster]); 

    const handleGacha = (ten) => {
        const cost = ten?6000:600;
        if(gameState.resources.orundum < cost) { toast.show("合成玉不足"); return null; }
        const newOps = [];
        let currentPity = gameState.pityCounter || 0;
        let lucky = false;
        for(let i=0; i<(ten?10:1); i++) {
            let rarity = 3;
            if (currentPity >= 50) { rarity = 7; currentPity = 0; } else {
                const r = Math.random();
                if (r > 0.99) { rarity = 7; if(!ten) lucky = true; } else if (r > 0.90) rarity = 6; else if (r > 0.60) rarity = 5; else if (r > 0.20) rarity = 4; else rarity = 3;
                if (rarity === 7) currentPity = 0; else currentPity++;
            }
            // Exclude Xingyuan from Gacha
            const pool = OPERATORS.filter(o=>o.rarity===rarity && o.id!=='supporter_xingyuan');
            const op = pool[Math.floor(Math.random()*pool.length)];
            const exist = gameState.roster.find(o=>o.id===op.id);
            let isNew = false;
            if(exist) {
                if(exist.potential < 6) newOps.push({...exist, potential:exist.potential+1});
                else { newOps.push(exist); toast.show("已满潜，转化为凭证"); }
            } else { newOps.push({...op, uuid: uuidv4(), level:1, potential:1, isNew: true}); isNew = true; }
        }
        setGameState(p => {
            let nextRoster = [...p.roster];
            newOps.forEach(n => { const idx = nextRoster.findIndex(o=>o.id===n.id); if(idx>=0) nextRoster[idx].potential = Math.min(6, nextRoster[idx].potential + (n.isNew?0:1)); else nextRoster.push({...n, uuid:uuidv4()}); });
            return { ...p, resources: {...p.resources, orundum: p.resources.orundum-cost}, roster: nextRoster, pityCounter: currentPity, stats: {...p.stats, luckyPull: p.stats.luckyPull || lucky} };
        });
        return newOps;
    };

    const handleRecruit = (tags, time) => {
          const recruitmentCount = (gameState.recruitmentCount || 0) + 1;
          let op = null;
          
          // Xingyuan Easter Egg Logic: Guaranteed on 10th recruit or small chance (0.5%)
          if (recruitmentCount % 10 === 0 || Math.random() < 0.005) {
              op = OPERATORS.find(o => o.id === 'supporter_xingyuan');
          } else {
              const validOps = OPERATORS.filter(op => {
                  if (op.id === 'supporter_xingyuan') return false; // Can't get normally
                  if (op.rarity > 6) return false; 
                  if (op.rarity === 6 && !tags.includes('高级资深干员')) return false;
                  if (op.rarity === 5 && !tags.includes('资深干员')) return false;
                  if (tags.length === 0) return true;
                  return tags.every(t => op.tags?.includes(t) || op.class === t || (t==='近战位' && op.type===OperatorType.MELEE) || (t==='远程位' && op.type===OperatorType.RANGED));
              });
              const pool = validOps.length > 0 ? validOps : OPERATORS.filter(o=>o.rarity===3);
              op = pool[Math.floor(Math.random()*pool.length)];
          }

          setGameState(p => {
            const exist = p.roster.find(o=>o.id===op.id);
            let nextRoster = [...p.roster];
            if(exist) exist.potential = Math.min(6, exist.potential+1); else nextRoster.push({...op, uuid: uuidv4(), level:1, potential:1});
            const nextTasks = p.tasks.map(t => t.id === 'weekly_recruit' ? { ...t, progress: t.progress + 1 } : t);
            // Ensure ticket exists and defaults to 0 if missing, prevent negative
            const currentTickets = (p.resources.recruitTicket || 0);
            return { ...p, roster: nextRoster, tasks: nextTasks, resources: {...p.resources, lmd: p.resources.lmd-1000, recruitTicket: currentTickets - 1}, recruitmentCount };
          });
          return op;
    };

    const collect = () => {
        const min = Math.floor((Date.now()-gameState.base.lastCollected)/60000);
        if(min<1) return;
        const tradeCount = gameState.base.assigned?.trade?.length || 0;
        const factoryCount = gameState.base.assigned?.factory?.length || 0;
        const lmdAmt = Math.floor(min * 500 * gameState.base.level * (1 + tradeCount * 0.2));
        const orundumAmt = Math.floor(min * 50 * gameState.base.level * (1 + factoryCount * 0.2));
        setGameState(p => ({ ...p, base: {...p.base, lastCollected:Date.now()}, resources: {...p.resources, lmd: p.resources.lmd + lmdAmt, orundum: p.resources.orundum + orundumAmt}}));
        toast.show(`资源已收取`);
    };
      
    const assignBase = (type, slot, opUuid) => {
        setGameState(p => {
            const base = p.base || {};
            const assigned = { trade: [], factory: [], ...(base.assigned || {}) };
            const targetArray = [...(assigned[type] || [])];
            targetArray[slot] = opUuid;
            return { ...p, base: { ...base, assigned: { ...assigned, [type]: targetArray } } };
        });
    };

    const upgrade = (uuid, amount = 1) => setGameState(p => { const op = p.roster.find(o=>o.uuid===uuid); if(!op || p.resources.lmd<1000*amount) return p; return { ...p, resources: {...p.resources, lmd: p.resources.lmd-1000*amount}, roster: p.roster.map(o => o.uuid===uuid ? {...o, level: o.level+amount} : o) }; });
    const equipSkin = (uuid, skinId) => setGameState(p => ({ ...p, roster: p.roster.map(o => o.uuid===uuid ? {...o, equippedSkinId: skinId} : o) }));
    const buyOriginium = (amt) => setGameState(p => ({ ...p, resources: { ...p.resources, originium: p.resources.originium + amt } }));
    const exchange = (t) => { if(gameState.resources.originium<1) return; setGameState(p=>({...p, resources: {...p.resources, originium: p.resources.originium-1, [t]: p.resources[t] + (t==='sanity'?1200:2000) }})); };
    const buySkin = (skinId, cost) => { setGameState(p => ({ ...p, resources: { ...p.resources, originium: p.resources.originium - cost }, ownedSkins: [...p.ownedSkins, skinId] })); }
      
    const startMission = (s) => { if(gameState.resources.sanity<10){toast.show("理智不足");return;} setGameState(p=>({...p, resources:{...p.resources, sanity:p.resources.sanity-10}, lastSquadIds: s.map(u=>u.uuid), tasks: p.tasks.map(t => t.id === 'daily_battle' ? { ...t, progress: t.progress + 1 } : t)})); setCurrentSquad(s); setView(2); };
    const win = (res) => { setGameState(p => { const nextTasks = p.tasks.map(t => t.id === 'daily_kill' ? { ...t, progress: t.progress + (res.stats?.killed||0) } : t); return { ...p, tasks: nextTasks, stats: { ...p.stats, totalKills: p.stats.totalKills+(res.stats?.killed||0), totalWins: p.stats.totalWins+(res.win?1:0), pitKills: p.stats.pitKills + (res.stats?.pitKills||0), customClears: p.stats.customClears + (res.win && selectedLvl.startsWith('C-') ? 1 : 0), maxEndlessWave: Math.max(p.stats.maxEndlessWave, res.stats?.endlessWave || 0) }, resources: { ...p.resources, lmd: p.resources.lmd+(res.rewards?.lmd||0), orundum: p.resources.orundum+(res.rewards?.orundum||0) }, completedLevels: res.win ? Array.from(new Set([...(p.completedLevels||[]), selectedLvl])) : p.completedLevels }; }); setView(0); };
    const claimTask = (id) => { setGameState(p => { const task = p.tasks.find(t=>t.id===id); const def = TASKS.find(t=>t.id===id); if (!task || !task.claimed && task.progress < def.target) return p; return { ...p, tasks: p.tasks.map(t => t.id===id ? {...t, claimed: false, progress: 0} : t), resources: { ...p.resources, lmd: p.resources.lmd + (def.reward.lmd || 0), orundum: p.resources.orundum + (def.reward.orundum || 0), recruitTicket: (p.resources.recruitTicket || 0) + (def.reward.recruitTicket || 0) } }; }); toast.show("奖励已领取，任务已刷新"); }

    const saveMap = (m) => { setGameState(p=>({...p, customMaps: [...p.customMaps, m]})); setView(0); };
    const deleteCustomMap = (id) => { if(confirm('确定删除该地图吗？')) setGameState(p=>({...p, customMaps: p.customMaps.filter(m=>m.id!==id)})); };
    const saveSquadPreset = (slot, ids) => { setGameState(p => ({...p, squadPresets: {...p.squadPresets, [slot]: ids}})); };
    const loadSquadPreset = (slot) => gameState.squadPresets[slot];

    return (
        <div className="w-full h-screen bg-black text-slate-100 overflow-hidden">
            {view===0 && <MainMenu gameState={gameState} roster={gameState.roster} onSelectLevel={()=>setView(6)} onNavigate={setView} onSetAssistant={(id) => setGameState(p=>({...p, assistantUuid: id}))} />}
            {view===6 && <LevelSelect completed={gameState.completedLevels||[]} customMaps={gameState.customMaps} onSelect={(id)=>{setSelectedLvl(id); setView(1)}} onBack={()=>setView(0)} challengeMode={challengeMode} setChallengeMode={setChallengeMode} onDeleteCustom={deleteCustomMap} />}
            {view===1 && <SquadSelect roster={gameState.roster} initialSquadIds={gameState.lastSquadIds} onBack={()=>setView(6)} onStart={startMission} onSavePreset={saveSquadPreset} onLoadPreset={loadSquadPreset} />}
            {view===13 && <SquadSelect roster={gameState.roster} initialSquadIds={gameState.lastSquadIds} onBack={()=>setView(0)} onSavePreset={saveSquadPreset} onLoadPreset={loadSquadPreset} editMode={true} />}
            {view===2 && selectedLvl && <GameLoop key={selectedLvl} levelId={selectedLvl} customLevel={gameState.customMaps.find(m=>m.id===selectedLvl)} squad={currentSquad} onExit={win} challengeMode={challengeMode} />}
            {view===3 && <Gacha resources={gameState.resources} pity={gameState.pityCounter || 0} onPull={handleGacha} onBack={()=>setView(0)}/>}
            {view===4 && <Base base={gameState.base} roster={gameState.roster} resources={gameState.resources} onCollect={collect} onAssign={assignBase} onBack={()=>setView(0)}/>}
            {view===5 && <Roster roster={gameState.roster} resources={gameState.resources} ownedSkins={gameState.ownedSkins} onUpgrade={upgrade} onEquipSkin={equipSkin} onBack={()=>setView(0)}/>}
            {view===7 && <Shop resources={gameState.resources} onBuy={buyOriginium} onExchange={exchange} onBuySkin={buySkin} ownedSkins={gameState.ownedSkins} onBack={()=>setView(0)} />}
            {view===8 && <Archive roster={gameState.roster} onBack={()=>setView(0)} />}
            {view===9 && <MapEditor onSave={saveMap} onBack={()=>setView(0)} />}
            {view===10 && <Achievements state={gameState} onBack={()=>setView(0)} />}
            {view===11 && <Recruitment resources={gameState.resources} onRecruit={handleRecruit} onBack={()=>setView(0)} />}
            {view===12 && <Tasks tasks={gameState.tasks} onClaim={claimTask} onBack={()=>setView(0)} />}
        </div>
    );
}

const root = createRoot(document.getElementById('root'));
root.render(<ToastProvider><App /></ToastProvider>);